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)