Handling Exceptions Raised in a Ruby Thread

Handling exceptions raised in a Ruby thread

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

How to catch exceptions inside threads

As Ajedi32 said, the problem is that the program is finishing before the thread has time to write "Exception".

There are two things to considerate here: Using Thread::abort_on_exception = true makes the program a lot easier to debug and avoids having nasty hidden bugs. The seconds one that I shouldn't be exiting the program without making sure every thread concluded correctly.
The code should instead be:

Thread::abort_on_exception = true 
my_thread = Thread.new do
begin
raise "Exception"
rescue => exc
print exc
end
end

#Do some parallel stuff
my_thread.join

Ruby threads: how to catch an exception without calling .join?

I figured out how to do it - I just need to wrap insides of my thread into a rescue block like that:

Thread.abort_on_exception = true

a = Thread.new do
begin #new
errory_code
rescue Exception => detail #new
puts detail.message #new
end #new
end

sleep 1 #so that thread has enough time to execute

Which is a horribly obvious solution, - but, since it took me so long to think of it, will hopefully help somebody.

Handle exceptions in concurrent-ruby thread pool

The following answer is from jdantonio from here https://github.com/ruby-concurrency/concurrent-ruby/issues/616

"
Most applications should not use thread pools directly. Thread pools are a low-level abstraction meant for internal use. All of the high-level abstractions in this library (Promise, Actor, etc.) all post jobs to the global thread pool and all provide exception handling. Simply pick the abstraction that best fits your use case and use it.

If you feel the need to configure your own thread pool rather than use the global thread pool, you can still use the high-level abstractions. They all support an :executor option which allows you to inject your custom thread pool. You can then use the exception handling provided by the high-level abstraction.

If you absolutely insist on posting jobs directly to a thread pool rather than using our high-level abstractions (which I strongly discourage) then just create a job wrapper. You can find examples of job wrappers in all our high-level abstractions, Rails ActiveJob, Sucker Punch, and other libraries which use our thread pools."

So how about an implementation with Promises ?
http://ruby-concurrency.github.io/concurrent-ruby/Concurrent/Promise.html
In your case it would look something like this:

promises = []
products.each do |product|
new_product = generate_new_prodcut

promises << Concurrent::Promise.execute do
store_in_db(new_product)
end
end

# .value will wait for the Thread to finish.
# The ! means, that all exceptions will be propagated to the main thread
# .zip will make one Promise which contains all other promises.
Concurrent::Promise.zip(*promises).value!

How do I catch an error from a thread and then re-throw that error when all the threads have completed?

To answer your question - no actual way as the library explicitly silences exceptions and there is no configuration for it.

A possible workaround would be to capture exceptions manually:

error = nil
pool = Concurrent::FixedThreadPool.new(1)
numbers.each do |number|
pool.post do
begin
some_dangerous_action(number)
rescue Exception => e
error = e
raise # still let the gem do its thing
end
end
end

pool.shutdown
pool.wait_for_termination

raise error if error

How to capture an exception from another thread

If you turn on abort_on_exception then you won't have a chance to catch it. You can, however, leave that off and simply catch it when you do the join operation on your thread.

thread = Thread.new do
raise "Uh oh"
end

begin
thread.join
rescue => e
puts "Caught exception: #{e}"
end

The alternative is to make the thread catch its own exception and save it somewhere you can fetch it from later. For instance:

exceptions = { }

Thread.new do
begin
raise "Uh oh"
rescue => e
exceptions[Thread.current] = e
end
end

sleep(1)

puts exceptions.inspect
# => {#<Thread:0x007f9832889920 dead>=>#<RuntimeError: Uh oh>}

Rescue simultaneous errors raised inside threads

Here are a few pieces of the puzzle here.


Firstly, the program only waits for the main thread to finish:

Thread.new { Thread.current.abort_on_exception = true; raise 'Oh, no!' }

puts 'Ready or not, here I come'

The above may or may not raise the error.


Secondly, if you join on a thread, an exception raised by that threads is re-raised by the joined thread from the #join method:

gollum = Thread.new { raise 'My precious!!!' }

begin
gollum.join
rescue => e
# Prints 'My precious!!!'
puts e.message
end

Now at that point, the execution is returned to the thread that joined. It is no longer joined to the thread that caused the error or any other thread. The reason it's not joined to other threads is because you can only join one thread at the time. threads.each(&:join) actually joins you to the first, when it ends - to the second and so on:

frodo = Thread.new { raise 'Oh, no, Frodo!' }
sam = Thread.new { raise 'Oh, no, Sam!' }

begin
[frodo, sam].each(&:join)
rescue => e
puts e.message
end

puts 'This is the end, my only friend, the end.'

The above prints

Oh, no, Frodo!

This is the end, my only friend, the end.


Now lets put it together:

frodo = Thread.new { Thread.current.abort_on_exception = true; raise 'Oh, no, Frodo!' }
sam = Thread.new { Thread.current.abort_on_exception = true; raise 'Oh, no, Sam!' }

begin
[frodo, sam].each(&:join)
rescue => e
puts e.message
end

puts 'This is the end, my only friend, the end.'

Many things can happen here. What is of importance is, if we manage to join (we don't get an error before that), the rescue will catch the exception in the main thread from whichever thread manages to raise it first and then continue after the rescue. After that, the main thread (and thus the program) may or may not finish before the other thread raises its exception.


Let examine some possible outputs:

ex.rb:1:in `block in ': Oh, no, Frodo! (RuntimeError)

Frodo raised his exception before we joined.

Oh, no, Sam!

This is the end, my only friend, the end.

After we joined, Sam was the first to raise an error. After we printed the error message in the main thread, we also printed the end. Then the main thread finished, before Frodo could raise his error.

Oh, no, Frodo!ex.rb:2:in `block in ': Oh, no, Sam! (RuntimeError)

We managed to join. Frodo was the first to raise, we rescued and printed. Sam raised before we could print the end.

Oh, no, Sam!

This is the end, my only friend, the end.ex.rb:1:in `block in ': Oh, no, Frodo! (RuntimeError)

(Very rarely) We managed to get to the rescue. Sam raised an error first and we printed it from the main thread. We printed the end. Just after the print, but before the main thread is terminated, Frodo managed to jab his error as well.


As for a possible solution, you just need as many rescues as there are threads that might raise. Note that I also put the thread creation in the safeguarded block to ensure we catch potential errors before joins as well:

def execute_safely_concurrently(number_of_threads, &work)
return if number_of_threads.zero?

begin
Thread.new(&work).join
rescue => e
puts e
end

execute_safely_concurrently(number_of_threads.pred, &work)
end

execute_safely_concurrently(2) do
Thread.current.abort_on_exception = true
raise 'Handle me, bitte!'
end

Ruby: how to handler thread exception?

Catch the exception inside the thread, and set the error in a variable accessible by the main thread (for testing you could use a global variable like so: $thread_error).

If the error-variable exists, then raise it from the main thread.

You could also use a queue to communicate between the threads, but then it wouldn't be able to utilize multiple threads.

require 'thread'

$temp = Thread.new do
begin
loop do
puts 'loop me'
begin
puts "try thread"
raise Exception.new('QwQ') if rand > 0.5
puts "skip try"
rescue
puts "QwQ"
end
sleep(0.5)
end
puts '...WTF'
rescue Exception => e
$thread_error = e
raise e
end
end

loop do
puts "runner #{Thread.list.length} #{$temp.status}"
raise $thread_error if $thread_error
sleep(2)
end


Related Topics



Leave a reply



Submit