Skip to content

Replace Timeout.timeout with Socket.tcp(..., open_timeout:) #223

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

osyoyu
Copy link
Contributor

@osyoyu osyoyu commented Jul 14, 2025

Resolves #6.

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

In Ruby <3.5 environments where Socket.tcp does not have the open_timeout option, I have kept the behavior unchanged. net/http will use Timeout.timeout { TCPSocket.open }.

Changes in behavior

The raised Net::OpenTimeout's message has slightly changed, and also carrys a Errno::ETIMEDOUT as its cause. I can keep the message compatible if needed.

/home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1668:in 'Net::HTTP#connect': Failed to open TCP connection to example.com:80 (Connection timed out - user specified timeout)
(Net::OpenTimeout)
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1636:in 'Net::HTTP#do_start'
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1625:in 'Net::HTTP#start'
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:2376:in 'Net::HTTP#request'
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1991:in 'Net::HTTP#get'
        from nethttptest.rb:13:in '<main>'
/home/osyoyu/.rbenv/versions/master/lib/ruby/3.5.0+2/socket.rb:898:in 'block in Socket.tcp_with_fast_fallback': Connection timed out - user specified timeout (Errno::ETIMEDOUT)
        from /home/osyoyu/.rbenv/versions/master/lib/ruby/3.5.0+2/socket.rb:728:in 'Kernel#loop'
        from /home/osyoyu/.rbenv/versions/master/lib/ruby/3.5.0+2/socket.rb:728:in 'Socket.tcp_with_fast_fallback'
        from /home/osyoyu/.rbenv/versions/master/lib/ruby/3.5.0+2/socket.rb:667:in 'Socket.tcp'
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1660:in 'Net::HTTP#connect'
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1636:in 'Net::HTTP#do_start'
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1625:in 'Net::HTTP#start'
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:2376:in 'Net::HTTP#request'
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1991:in 'Net::HTTP#get'
        from nethttptest.rb:13:in '<main>'

Previously, it looked like this.

/home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1663:in 'TCPSocket#initialize': Failed to open TCP connection to example.com:80 (execution expired) (Net::OpenTimeout)
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1663:in 'IO.open'
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1663:in 'block in Net::HTTP#connect'
        from /home/osyoyu/.rbenv/versions/3.4.4/lib/ruby/3.4.0/timeout.rb:185:in 'block in Timeout.timeout'
        from /home/osyoyu/.rbenv/versions/3.4.4/lib/ruby/3.4.0/timeout.rb:192:in 'Timeout.timeout'
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1662:in 'Net::HTTP#connect'
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1636:in 'Net::HTTP#do_start'
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1625:in 'Net::HTTP#start'
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:2376:in 'Net::HTTP#request'
        from /home/osyoyu/Development/rubyorg/net-http/lib/net/http.rb:1991:in 'Net::HTTP#get'
        from nethttptest.rb:13:in '<main>'

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
@osyoyu
Copy link
Contributor Author

osyoyu commented Jul 14, 2025

The failing test is caused by https://bugs.ruby-lang.org/issues/21512. We can wait until it gets fixed

@osyoyu osyoyu force-pushed the socket-tcp-open-timeout branch from 3059071 to 00cb12b Compare July 16, 2025 01:24
@osyoyu
Copy link
Contributor Author

osyoyu commented Jul 19, 2025

Replaced by #224

@osyoyu osyoyu closed this Jul 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Replacing Timeout.timeout in Net::HTTP#connect with socket timeout options
1 participant