Dmytro Shteflyuk created THRIFT-6125:
----------------------------------------

             Summary: Ruby client leaves transports reusable after uncertain 
request sends
                 Key: THRIFT-6125
                 URL: https://issues.apache.org/jira/browse/THRIFT-6125
             Project: Thrift
          Issue Type: Bug
          Components: Ruby - Library
            Reporter: Dmytro Shteflyuk
            Assignee: Dmytro Shteflyuk


h3. Problem

The Ruby client closes its output transport when argument serialization fails, 
but not when writing the message envelope, finishing the message, or flushing 
the transport fails.

Once message writing has begun, an exception does not reliably indicate whether 
zero, some, or all request bytes reached the underlying connection. Leaving 
that connection open allows a later call to reuse transport state whose wire 
outcome is unknown. A buffered transport may still contain request data, while 
an unbuffered transport may already have emitted part or all of the request.

Argument construction is different: when creating or assigning the generated 
arguments fails before message writing starts, no bytes have been published and 
the connection remains safe to reuse.

h3. Client impact

After a write or flush error, an application may catch the exception and make 
another call with the same generated client. Because the Ruby client currently 
leaves the transport open, that later request can follow incomplete or retained 
data from the failed request. This can desynchronize the client and server or 
make it unclear which request a subsequent response belongs to.

The behavior affects both ordinary calls and oneway calls. For ordinary calls, 
a failed send must also not add a sequence ID to the pending-reply queue.

h3. Reproduction

The following example uses a transport that accepts request bytes but fails 
during flush:

{code:ruby}
require "thrift"

class FailingFlushTransport < Thrift::BaseTransport
  attr_reader :closed

  def write(_data)
  end

  def flush
    raise Thrift::TransportException.new(
      Thrift::TransportException::UNKNOWN,
      "flush failed"
    )
  end

  def close
    @closed = true
  end
end

class EmptyArgs
  def write(_protocol)
  end
end

class ExampleClient
  include Thrift::Client
end

transport = FailingFlushTransport.new
protocol = Thrift::BinaryProtocol.new(transport)
client = ExampleClient.new(protocol)

begin
  client.send_message("example", EmptyArgs)
rescue Thrift::TransportException => error
  puts "#{error.message}; closed=#{transport.closed.inspect}"
end
{code}

The current implementation prints:

{noformat}
flush failed; closed=nil
{noformat}

The transport therefore remains available for another request even though 
delivery of the failed request is uncertain.

h3. Expected behavior

The client should construct and assign arguments before beginning message 
output. Failures at that stage should leave the transport open and should not 
consume a sequence ID.

After message output begins, an exception from the envelope, argument 
serialization, message end, or flush should close the output transport and 
re-raise the original exception. A failure while closing must not replace the 
original send error. Ordinary calls should enter the pending-reply queue only 
after a successful flush, and oneway calls should follow the same 
transport-closing rule.




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

Reply via email to