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)