diff options
| author | Daisuke Aritomo <osyoyu@osyoyu.com> | 2025-07-17 00:09:47 +0900 |
|---|---|---|
| committer | git <svn-admin@ruby-lang.org> | 2025-11-10 02:40:46 +0000 |
| commit | f710e6bb54a1e2cfe808222bc8d70d8f68ab5dc9 (patch) | |
| tree | e265246cc436a3cc54d04f7ae693df7a211f24e1 | |
| parent | 8fa29a75abf265c20cdeb9779bf25c1b3eafcf26 (diff) | |
[ruby/net-http] Replace Timeout.timeout with TCPSocket.open(open_timeout:) when available
This patch replaces the implementation of #open_timeout from Timeout.timeout from the builtin timeout in TCPSocket.open, which was introduced in Ruby 3.5 (https://bugs.ruby-lang.org/issues/21347).
The builtin timeout in TCPSocket.open is better in several ways than Timeout.timeout. It does not rely on a separate Ruby Thread for monitoring Timeout (which is what the timeout library internally does).
Furthermore, it is compatible with Ractors, as opposed to Timeout.timeout (it internally uses Thread::Mutex which can not be used in non-main Ractors).
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
In Ruby <3.5 environments where `TCPSocket.open` does not have the `open_timeout` option, I have kept the behavior unchanged. net/http will use `Timeout.timeout { TCPSocket.open }`.
https://github.com/ruby/net-http/commit/728eb8fc42
| -rw-r--r-- | lib/net/http.rb | 22 |
1 files changed, 15 insertions, 7 deletions
diff --git a/lib/net/http.rb b/lib/net/http.rb index 7efb468e78..d6db9ba132 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1672,14 +1672,22 @@ module Net #:nodoc: end debug "opening connection to #{conn_addr}:#{conn_port}..." - s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { - begin - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - rescue => e - raise e, "Failed to open TCP connection to " + - "#{conn_addr}:#{conn_port} (#{e.message})" + begin + s = begin + # Use built-in timeout in TCPSocket.open if available + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) + rescue ArgumentError => e + raise if !e.message.include?('unknown keyword: :open_timeout') + # Fallback to Timeout.timeout if TCPSocket.open does not support open_timeout + Timeout.timeout(@open_timeout, Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } end - } + rescue => e + e = Net::OpenTimeout.new(e) if e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions + raise e, "Failed to open TCP connection to " + + "#{conn_addr}:#{conn_port} (#{e.message})" + end s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) debug "opened" if use_ssl? |
