Dmytro Shteflyuk created THRIFT-6127:
----------------------------------------

             Summary: Ruby sockets remain reusable after I/O timeouts
                 Key: THRIFT-6127
                 URL: https://issues.apache.org/jira/browse/THRIFT-6127
             Project: Thrift
          Issue Type: Bug
          Components: Ruby - Library
            Reporter: Dmytro Shteflyuk
            Assignee: Dmytro Shteflyuk


h3. Problem

When a timed Ruby socket read or write raises 
{{Thrift::TransportException::TIMED_OUT}}, the transport leaves its underlying 
socket open.

This is unsafe once the operation has made partial progress. A read may already 
have removed bytes from the stream before a later read times out, while a write 
timeout may occur after some request bytes have been delivered. The 
connection's message boundary or delivery state is then uncertain, but 
{{open?}} still reports true and later operations are allowed to reuse it.

The behavior is shared by {{Thrift::Socket}} and its TLS and UNIX socket 
subclasses. It also applies to sockets returned by the Ruby server transports.

h3. Client impact

Applications that catch a timeout may continue using a connection whose stream 
is no longer aligned with the Thrift message the application expected. A later 
request or response can consequently fail with an unrelated decoding or 
transport error, and a timed-out write cannot be retried safely on the same 
connection because its partial-delivery state is unknown.

Callers should receive the original timeout and reconnect before performing 
more I/O.

h3. Reproduction

>From {{lib/rb}}, run:

{code:ruby}
require "thrift"
require "socket"

local, peer = ::Socket.pair(:UNIX, :STREAM, 0)
transport = Thrift::Socket.new
transport.handle = local
transport.timeout = 0.05

peer.write("A")

begin
  transport.read_all(2)
rescue Thrift::TransportException => error
  puts "#{error.class}: #{error.message}"
  puts "open=#{transport.open?}"
ensure
  transport.close
  peer.close
end
{code}

The peer supplies one byte, so {{read_all(2)}} makes partial progress before 
waiting for the missing byte. Before this change, the operation raises a 
timeout but prints:

{noformat}
Thrift::TransportException: Socket: Timed out reading 1 bytes from 
localhost:9090
open=true
{noformat}

The equivalent write path also remains open after partially writing a request 
and then timing out.

h3. Expected behavior

A timed read or write must preserve the original {{TIMED_OUT}} exception and 
close the affected transport. {{open?}} should then return false, subsequent 
I/O should fail with {{NOT_OPEN}}, and the peer should observe the closed 
connection.

Successful reads and writes, including partial operations that complete before 
the deadline, should remain unchanged.




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

Reply via email to