I am looking for a solution of classic problem of exception handling. Consider following piece of code:
def foo(n)
puts \" for #{n}\"
sleep n
raise \"a
This will wait for the first thread to either raise or return (and re-raise):
require 'thwait'
def wait_for_first_block_to_complete(*blocks)
threads = blocks.map do |block|
Thread.new do
block.call
rescue StandardError
$!
end
end
waiter = ThreadsWait.new(*threads)
value = waiter.next_wait.value
threads.each(&:kill)
raise value if value.is_a?(StandardError)
value
end
Postponed exceptions processing (Inspired by @Jason Ling)
class SafeThread < Thread
def initialize(*args, &block)
super(*args) do
begin
block.call
rescue Exception => e
@exception = e
end
end
end
def join
raise_postponed_exception
super
raise_postponed_exception
end
def raise_postponed_exception
Thread.current.raise @exception if @exception
end
end
puts :start
begin
thread = SafeThread.new do
raise 'error from sub-thread'
end
puts 'do something heavy before joining other thread'
sleep 1
thread.join
rescue Exception => e
puts "Caught: #{e}"
end
puts 'proper end'
Thread.class_eval do
alias_method :initialize_without_exception_bubbling, :initialize
def initialize(*args, &block)
initialize_without_exception_bubbling(*args) {
begin
block.call
rescue Exception => e
Thread.main.raise e
end
}
end
end
If you want any unhandled exception in any thread to cause the interpreter to exit, you need to set Thread::abort_on_exception= to true
. Unhandled exception cause the thread to stop running. If you don't set this variable to true, exception will only be raised when you call Thread#join
or Thread#value
for the thread. If set to true it will be raised when it occurs and will propagate to the main thread.
Thread.abort_on_exception=true # add this
def foo(n)
puts " for #{n}"
sleep n
raise "after #{n}"
end
begin
threads = []
[15, 5, 20, 3].each do |i|
threads << Thread.new do
foo(i)
end
end
threads.each(&:join)
rescue Exception => e
puts "EXCEPTION: #{e.inspect}"
puts "MESSAGE: #{e.message}"
end
Output:
for 5
for 20
for 3
for 15
EXCEPTION: #<RuntimeError: after 3>
MESSAGE: after 3
Note: but if you want any particular thread instance to raise exception this way there are similar abort_on_exception= Thread instance method:
t = Thread.new {
# do something and raise exception
}
t.abort_on_exception = true