You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@thrift.apache.org by "Dmytro Shteflyuk (Jira)" <ji...@apache.org> on 2026/07/26 19:45:00 UTC

[jira] [Created] (THRIFT-6123) Ruby Serializer retains JSON protocol state after write failures

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)