You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Timeout::ExitException can be raised by nested timeouts #52
shows that, when an ExitException is raised, the "inner" exception is being caught by the "outer" handle_timeout block. Suspicious this is the result of indeterminate Thread#raise behavior?
Environment
Ruby 3.3.5
Timeout 0.4.1
Ubuntu 24.04.1 running on WSL2
👋 We are experiencing this issue in our setup. But our experiments show that this only happens if both timeout calls have the same timeout value, for example the following test:
Fails all the time with a Timeout::ExitException that RSpec is not able to rescue:
Failures:
1) Timeout times out
Failure/Error: output.__send__(method, *args, &block)
Timeout::ExitException:
execution expired
# Backtrace omitted ...
#
# Showing full backtrace because every line was filtered out.
# See docs for RSpec::Configuration#backtrace_exclusion_patterns and
# RSpec::Configuration#backtrace_inclusion_patterns for more information.
Finished in 2.01 seconds (files took 0.13324 seconds to load)
2 examples, 1 failure
However, doing this change:
Timeout.timeout(2) do
- Timeout.timeout(2) do+ Timeout.timeout(2.1) do
sleep 19
Makes the test pass 100% of the time:
.
Finished in 2.01 seconds (files took 0.1275 seconds to load)
1 example, 0 failures
$ ruby -v -Ilib repro_nesting1.rb
ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [x86_64-linux]
#<Timeout::Error: execution expired>
repro_nesting1.rb:10:in `p': execution expired (Timeout::ExitException)
from repro_nesting1.rb:10:in `rescue in <main>'
from repro_nesting1.rb:3:in `<main>'
So the bad thing is the exception happens on line 10 which is p e and is after the Timeout.timeout blocks.
This is due to having multiple Thread#raise, as explained in https://bugs.ruby-lang.org/issues/20314
I think one idea would be to avoid calling Thread#raise in between another Thread#raise by Timeout and the Request#finished call, maybe using a lock per Fiber or so.
I need to try it.
$ ruby -v -Ilib repro_nesting1.rb
ruby 3.4.7 (2025-10-08 revision 7a5688e2a2) +PRISM [x86_64-linux]
:HERE
>/home/eregon/code/timeout/lib/timeout.rb:40:in 'Timeout::Error.handle_timeout': execution expired (Timeout::Error)
from /home/eregon/code/timeout/lib/timeout.rb:363:in 'Timeout.timeout'
from repro_nesting1.rb:4:in '<main>'
repro_nesting1.rb:6:in 'Kernel#sleep': execution expired (Timeout::ExitException)
from repro_nesting1.rb:6:in 'block (2 levels) in <main>'
from /home/eregon/code/timeout/lib/timeout.rb:353:in 'block (2 levels) in Timeout.timeout'
from /home/eregon/code/timeout/lib/timeout.rb:161:in 'Timeout::FiberState#interruptible'
from /home/eregon/code/timeout/lib/timeout.rb:209:in 'Timeout::Request#interruptible'
from /home/eregon/code/timeout/lib/timeout.rb:352:in 'block in Timeout.timeout'
from /home/eregon/code/timeout/lib/timeout.rb:38:in 'Timeout::Error.handle_timeout'
from /home/eregon/code/timeout/lib/timeout.rb:363:in 'Timeout.timeout'
from repro_nesting1.rb:5:in 'block in <main>'
from /home/eregon/code/timeout/lib/timeout.rb:353:in 'block (2 levels) in Timeout.timeout'
from /home/eregon/code/timeout/lib/timeout.rb:161:in 'Timeout::FiberState#interruptible'
from /home/eregon/code/timeout/lib/timeout.rb:209:in 'Timeout::Request#interruptible'
from /home/eregon/code/timeout/lib/timeout.rb:352:in 'block in Timeout.timeout'
from /home/eregon/code/timeout/lib/timeout.rb:38:in 'Timeout::Error.handle_timeout'
from /home/eregon/code/timeout/lib/timeout.rb:363:in 'Timeout.timeout'
from repro_nesting1.rb:4:in '<main>'
Timeout::Error
Activity
eregon commentedon Oct 19, 2024
It sounds somewhat related to https://bugs.ruby-lang.org/issues/20314
I'll try to think about a fix when I have some time.
hsbt commentedon Dec 3, 2024
FYI: ruby/ruby#10851
sergio-bobillier commentedon Jun 6, 2025
👋 We are experiencing this issue in our setup. But our experiments show that this only happens if both
timeoutcalls have the same timeout value, for example the following test:Fails all the time with a
Timeout::ExitExceptionthat RSpec is not able torescue:However, doing this change:
Makes the test pass 100% of the time:
eregon commentedon Dec 11, 2025
Taking a look at this and with:
I get this consistently:
FWIW on 3.3 it's slightly different:
So the bad thing is the exception happens on line 10 which is
p eand is after the Timeout.timeout blocks.This is due to having multiple Thread#raise, as explained in https://bugs.ruby-lang.org/issues/20314
eregon commentedon Dec 11, 2025
I think one idea would be to avoid calling Thread#raise in between another Thread#raise by Timeout and the
Request#finishedcall, maybe using a lock per Fiber or so.I need to try it.
eregon commentedon Dec 22, 2025
I made a draft PR to fix this: #91
It works correctly for this example:
feat: Timeout option for http client (#76)