Skip to content

Commit 00cb12b

Browse files
committed
Replace Timeout.timeout with Socket.tcp(..., open_timeout:)
This patch replaces the implementation of #open_timeout from Timeout.timeout from the builtin timeout in Socket.tcp, which was introduced in Ruby 3.5 (https://bugs.ruby-lang.org/issues/21347). The builtin timeout in Socket.tcp is better in several ways. First, it does not rely on a separate Ruby Thread for monitoring Timeout (which is what the timeout library internally does). Also, it is compatible with Ractors, since it does not rely on Mutexes (which is also what the timeout library does). This change allows the following code to work. require 'net/http' Ractor.new { uri = URI('http://example.com/') http = Net::HTTP.new(uri.host, uri.port) http.open_timeout = 1 http.get(uri.path) }.value
1 parent 30e6b5f commit 00cb12b

File tree

2 files changed

+15
-9
lines changed

2 files changed

+15
-9
lines changed

lib/net/http.rb

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,14 +1654,20 @@ def connect
16541654
end
16551655

16561656
debug "opening connection to #{conn_addr}:#{conn_port}..."
1657-
s = Timeout.timeout(@open_timeout, Net::OpenTimeout) {
1658-
begin
1659-
TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
1660-
rescue => e
1661-
raise e, "Failed to open TCP connection to " +
1662-
"#{conn_addr}:#{conn_port} (#{e.message})"
1657+
begin
1658+
# Use built-in timeout in Socket.tcp if available
1659+
s = if Socket.method(:tcp).parameters.any? {|param| param[0] == :key && param[1] == :open_timeout }
1660+
Socket.tcp(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout)
1661+
else
1662+
Timeout.timeout(@open_timeout, Net::OpenTimeout) {
1663+
TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
1664+
}
16631665
end
1664-
}
1666+
rescue => e
1667+
e = Net::OpenTimeout.new(e) if e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions
1668+
raise e, "Failed to open TCP connection to " +
1669+
"#{conn_addr}:#{conn_port} (#{e.message})"
1670+
end
16651671
s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
16661672
debug "opened"
16671673
if use_ssl?

test/net/http/test_http.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,8 @@ def test_failure_message_includes_failed_domain_and_port
246246
# hostname to be included in the error message
247247
host = Struct.new(:to_s).new("<example>")
248248
port = 2119
249-
# hack to let TCPSocket.open fail
250-
def host.to_str; raise SocketError, "open failure"; end
249+
# hack to let Socket.tcp fail
250+
def host.match?(_); raise SocketError, "open failure"; end
251251
uri = Struct.new(:scheme, :hostname, :port).new("http", host, port)
252252
assert_raise_with_message(SocketError, /#{host}:#{port}/) do
253253
TestNetHTTPUtils.clean_http_proxy_env{ Net::HTTP.get(uri) }

0 commit comments

Comments
 (0)