Skip to content

Skip Timeout.timeout wrap when TCPSocket native connect_timeout is available#840

Open
Linuus wants to merge 2 commits into
httprb:mainfrom
Linuus:skip-timeout-wrap-when-native
Open

Skip Timeout.timeout wrap when TCPSocket native connect_timeout is available#840
Linuus wants to merge 2 commits into
httprb:mainfrom
Linuus:skip-timeout-wrap-when-native

Conversation

@Linuus
Copy link
Copy Markdown
Contributor

@Linuus Linuus commented May 25, 2026

We are always wrapping in Timeout.timeout when a connect_timeout is set, even when native socket connect_timeout is available. The native timeout already enforces the limit and raises IO::TimeoutError, so the wrap is redundant.

Using Timeout.timeout is problematic: under certain exit paths, the Timeout::Request is not removed from the Timeout watcher's internal request queue, retaining the target Thread for the process lifetime. In long-running workers (Sidekiq, Puma in some configurations) this surfaces as slow memory growth.

Looks like this was meant to be avoided when native timeout support was added - the comment above the method even says "Falls back to Timeout.timeout on older Rubies or with custom socket classes" - but the code applies the wrap unconditionally. This PR makes the behavior match the comment.

I think this PR basically resolves #542

Linuus added 2 commits May 25, 2026 12:16
…ailable

When a connect_timeout is provided on Ruby 3.4+ with TCPSocket, the
socket's native connect_timeout already enforces the timeout and raises
IO::TimeoutError on expiration. The outer Timeout.timeout wrap is
therefore redundant when native is in use, and creates a
Timeout::Request
that can leak under abnormal exit paths, pinning the calling Thread for
the process lifetime. In long-running workers (Sidekiq, etc.) this
results in slow memory growth from accumulated Threads.

This matches the existing method comment's intent ("Falls back to
Timeout.timeout on older Rubies or with custom socket classes") which
was previously not what the code actually did — the wrap was applied
unconditionally whenever a connect_timeout was set.
@sferik sferik force-pushed the main branch 2 times, most recently from fc0ff9d to 110e972 Compare May 26, 2026 18:21
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.

Remove Timeout.timeout

1 participant