From: nagachika00@... Date: 2017-03-19T21:07:59+00:00 Subject: [ruby-core:80236] [Ruby trunk Bug#12741] Timeout with specified exception class sets cause on error if timeout block is handling an exception when timeout occurs Issue #12741 has been updated by nagachika (Tomoyuki Chikanaga). 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. ---------------------------------------- Bug #12741: Timeout with specified exception class sets cause on error if timeout block is handling an exception when timeout occurs https://bugs.ruby-lang.org/issues/12741#change-63674 * Author: rockfx01 (Tim Mertens) * Status: Closed * Priority: Normal * Assignee: * Target version: * ruby -v: ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin14] * Backport: 2.1: UNKNOWN, 2.2: REQUIRED, 2.3: DONE ---------------------------------------- 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: ~~~ ruby 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 `
' => 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] -- https://bugs.ruby-lang.org/ Unsubscribe: