Skip to content

Map IO::TimeoutError to Net::OpenTimeout - #770

Merged
nevans merged 2 commits into
ruby:masterfrom
jbourassa:open-timeout-io-timeout-error
Sep 26, 2026
Merged

nevans merged 2 commits into
ruby:masterfrom
jbourassa:open-timeout-io-timeout-error

Conversation

@jbourassa

Copy link
Copy Markdown
Contributor

Since Ruby 4.0, Ruby raises an IO::TimeoutError when the open_timeout is reached.

The fix: Similar to Net::HTTP in ruby/net-http#263, map those errors to Net::TimeoutError. This case is simpler though because net-imap only supports Ruby 3.2, and IO::TimeoutError exists in 3.2: https://docs.ruby-lang.org/en/3.2/IO/TimeoutError.html

Before -- notice the exception class at the end of the line:

mise exec ruby@3.4.10 -- ruby -Ilib -rnet/imap -e 'Net::IMAP.new("imap.gmail.com", open_timeout: 0)'
./lib/net/imap.rb:3706:in 'Net::IMAP#tcp_socket': Timeout to open TCP connection to imap.gmail.com:143 (exceeds 0 seconds) (Net::OpenTimeout)

mise exec ruby@4.0.7 -- ruby -Ilib -rnet/imap -e 'Net::IMAP.new("imap.gmail.com", open_timeout: 0)'
~/.local/share/mise/installs/ruby/4.0.7/lib/ruby/4.0.0/socket.rb:930:in 'block in Socket.tcp_with_fast_fallback': user specified timeout for imap.gmail.com:143 (IO::TimeoutError)

After

mise exec ruby@4.0.7 -- ruby -Ilib -rnet/imap -e 'Net::IMAP.new("imap.gmail.com", open_timeout: 0)'
./lib/net/imap.rb:3706:in 'Net::IMAP#tcp_socket': Timeout to open TCP connection to imap.gmail.com:143 (exceeds 0 seconds) (Net::OpenTimeout)

For tests, the only reasonable way I found requires using a non-loopback ip; I don't know if we want that in CI; so I left it out.

Since Ruby 4.0, Ruby raises an `IO::TimeoutError` when the `open_timeout`
is reached.

The fix: Similar to `Net::HTTP` in ruby/net-http#263, map those errors
to `Net::TimeoutError`. This case is simpler though because net-imap
only supports Ruby 3.2, and `IO::TimeoutError` exists in 3.2:
https://docs.ruby-lang.org/en/3.2/IO/TimeoutError.html
@nevans

nevans commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

@jbourassa Thanks! I wasn't quite sure if we want to move forward with IO::TimeoutError in new ruby versions, or maintain backward compatibility by mapping that to Net::OpenTimeout. But, I'm happy to follow net-http's lead on this.

For testing... what do you think of this test?

  def test_timeout
    host = ENV.fetch("TEST_CONNECT_TIMEOUT_HOST", "10.255.255.1")
    pend host if host.start_with? "skip"
    assert_raise(Net::OpenTimeout) do
      Net::IMAP.new(host, open_timeout: 0)
    end
  end

I used 10.255.255.1 IP address because that's what's used in the tests in https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts, and I expect we can use that in most dev/test/CI environments. But I do wonder if it'd be better to use an IP address from one of the officially designated TEST subnets [RFC5737].

In other environments, we can set the environment variable to something appropriate, e.g: TEST_CONNECT_TIMEOUT_HOST=192.0.2.1 or TEST_CONNECT_TIMEOUT_HOST="skipped on Windows in CI".

@nevans nevans changed the title Map IO::TimeoutError to Net::TimeoutError Map IO::TimeoutError to Net::OpenTimeout Sep 26, 2026
nevans
nevans previously approved these changes Sep 26, 2026
@nevans
nevans force-pushed the open-timeout-io-timeout-error branch from 192a17d to 525656d Compare September 26, 2026 19:39
@nevans
nevans merged commit f581695 into ruby:master Sep 26, 2026
17 checks passed
@jbourassa

Copy link
Copy Markdown
Contributor Author

For testing... what do you think of this test?

Sounds great, well done.

Thanks for looking at the PR and more generally your work on net-imap.

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.

2 participants