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
Selenium Scroll Element into (Center Of) View
Creating Permutations from a Multi-Dimensional Array in Ruby
Using Rbenv Doesn't Work with Sudo
How to Access Nested Elements of a Hash with a Single String Key
Converting a Hash into a Nested Hash
What Are <-- Ruby Strings Called? and How to Insert Variables in Them
How to Know If Today's Date Is in a Date Range
Descending Sort by Value of a Hash in Ruby
Why Is Enumerable#Each_With_Object Deprecated
Xpath Expression for Regex-Like Matching
Ruby Hash Autovivification (Facets)
Optional Arguments with Default Value in Ruby
Rails: Unable to Access Log File
Bundle Install Issue with Libv8 and Rails