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)