Dmytro Shteflyuk created THRIFT-6123:
----------------------------------------

             Summary: Ruby Serializer retains JSON protocol state after write 
failures
                 Key: THRIFT-6123
                 URL: https://issues.apache.org/jira/browse/THRIFT-6123
             Project: Thrift
          Issue Type: Bug
          Components: Ruby - Library
            Reporter: Dmytro Shteflyuk
            Assignee: Dmytro Shteflyuk


h3. Problem

{{Thrift::Serializer}} reuses both a memory transport and a protocol object 
across calls. It clears the transport buffer before each serialization, but it 
does not reset protocol-owned state.

{{JsonProtocol}} keeps a stack of container contexts and separator state. If an 
object's {{write}} method raises after beginning a message, struct, field, or 
container, those contexts are not unwound. Clearing only the output buffer 
leaves the protocol positioned inside the failed value.

A later, otherwise unrelated serialization can therefore start with a stale 
comma or colon. Some failure positions allow the first later result to look 
correct while leaving stale state behind for another call, so recovery must 
remain correct across consecutive serializations.

h3. Client impact

Applications that retain a Ruby {{Serializer}} can receive the expected 
exception for one failed JSON write and then unknowingly emit malformed JSON 
for later objects. Replacing the serializer avoids the problem, but callers 
have no indication that the existing serializer has become unusable.

h3. Reproduction

This example uses the generated {{SpecNamespace::Hello}} fixture from the Ruby 
specs:

{code:ruby}
class BrokenValue
  def write(protocol)
    protocol.write_struct_begin("Broken")
    protocol.write_field_begin("value", Thrift::Types::STRING, 1)
    raise "write failed"
  end
end

factory = Thrift::JsonProtocolFactory.new
serializer = Thrift::Serializer.new(factory)

begin
  serializer.serialize(BrokenValue.new)
rescue RuntimeError
end

value = SpecNamespace::Hello.new(greeting: "ok")
reused = serializer.serialize(value)
fresh = Thrift::Serializer.new(factory).serialize(value)

puts reused.inspect
# ":{\"1\":{\"str\":\"ok\"}}"

puts fresh.inspect
# "{\"1\":{\"str\":\"ok\"}}"
{code}

The reused serializer's payload begins with a colon and cannot be read as a 
Thrift JSON struct.

h3. Expected behavior

A failed serialization should preserve and re-raise the original exception 
without leaving the serializer poisoned. The next and subsequent calls should 
produce the same parseable bytes as a fresh serializer.

Successful calls should continue reusing their existing protocol. A protocol 
that participated in a failed write should be discarded and recreated lazily 
before the next call.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to