Bug #12741
closedTimeout with specified exception class sets cause on error if timeout block is handling an exception when timeout occurs
Description
If Timeout.timeout
is called with an exception class explicitly specified in the second argument, then if a timeout does occur while the underlying code wrapped by the Timeout block is handling an exception, that exception will bubble up to the error raised by the Timeout block and returned as the cause
of the Timeout error, when in fact the two are not related.
For example, given the following method:
require 'timeout'
OuterError = Class.new(StandardError)
InnerError = Class.new(StandardError)
def timeout_error_includes_cause?
begin
Timeout.timeout 0.1, OuterError do
loop do
begin
raise InnerError, 'An Error'
rescue
sleep 0.01
end
end
end
rescue => f
if f.cause
puts f.cause.class
puts f.cause.message
puts f.backtrace.join "\n"
end
!!f.cause
end
end
Then when we call this method, we will see that >99% of the time, the error raised by the Timeout block will include the StandardError raised by the inner block as the cause
of the OuterError, because the inner block is still in the process of handling the exception:
2.3.0 :025 > timeout_error_includes_cause?
InnerError
An Error
(irb):12:in `sleep'
(irb):12:in `rescue in block (2 levels) in timeout_error_includes_cause?'
(irb):9:in `block (2 levels) in timeout_error_includes_cause?'
(irb):8:in `loop'
(irb):8:in `block in timeout_error_includes_cause?'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/timeout.rb:101:in `timeout'
(irb):7:in `timeout_error_includes_cause?'
(irb):25:in `irb_binding'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb/workspace.rb:87:in `eval'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb/workspace.rb:87:in `evaluate'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb/context.rb:380:in `evaluate'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb.rb:489:in `block (2 levels) in eval_input'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb.rb:623:in `signal_status'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb.rb:486:in `block in eval_input'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb/ruby-lex.rb:246:in `block (2 levels) in each_top_level_statement'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb/ruby-lex.rb:232:in `loop'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb/ruby-lex.rb:232:in `block in each_top_level_statement'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb/ruby-lex.rb:231:in `catch'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb/ruby-lex.rb:231:in `each_top_level_statement'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb.rb:485:in `eval_input'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb.rb:395:in `block in start'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb.rb:394:in `catch'
~/.rvm/rubies/ruby-2.3.0/lib/ruby/2.3.0/irb.rb:394:in `start'
~/.rvm/rubies/ruby-2.3.0/bin/irb:11:in `<main>'
=> true
If we remove OuterError
from the Timeout.timeout
argument list, then the resulting Timeout::Error
will never include the nested exception as a cause
of the error raised by Timeout.
This has been tested and confirmed in the following ruby versions:
- ruby 2.2.4p230 (2015-12-16 revision 53155) [x86_64-darwin15]
- ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin14]
Updated by nobu (Nobuyoshi Nakada) about 8 years ago
- Status changed from Open to Closed
Applied in changeset r56125.
thread.c: set cause by Thread#raise
- thread.c (rb_threadptr_raise): set cause from the called thread,
but not from the thread to be interrupted.
[ruby-core:77222] [Bug #12741]
Updated by nagachika (Tomoyuki Chikanaga) over 7 years ago
- Backport changed from 2.1: UNKNOWN, 2.2: UNKNOWN, 2.3: UNKNOWN to 2.1: UNKNOWN, 2.2: REQUIRED, 2.3: REQUIRED
Updated by nagachika (Tomoyuki Chikanaga) over 7 years ago
- Backport changed from 2.1: UNKNOWN, 2.2: REQUIRED, 2.3: REQUIRED to 2.1: UNKNOWN, 2.2: REQUIRED, 2.3: DONE
ruby_2_3 r58026 merged revision(s) 56125,56150.
Updated by usa (Usaku NAKAMURA) over 7 years ago
- Backport changed from 2.1: UNKNOWN, 2.2: REQUIRED, 2.3: DONE to 2.1: UNKNOWN, 2.2: DONE, 2.3: DONE
ruby_2_2 r58107 merged revision(s) 56125,56150.