This blog is part of our Ruby 2.5 series.
Let’s see what happens when an exception is raised inside a thread.
Execution of it looks like this.
Note that the last two lines from the block were not printed. Also notice that after failing in the thread the program continued to run in main thread. That’s why we got the message “In the main thread”.
This is because the default behavior of Ruby is to silently ignore exceptions in threads and then to continue to execute in the main thread.
Enabling abort_on_exception to stop on failure
If we want an exception in a thread to stop further processing
both in the thread and in the main thread then we can enable
Thread[.#]abort_on_exception on that thread to achieve that.
Notice that in the below code we are using
As we can see once an exception was encountered in the thread then processing stopped on both in the thread and in the main thread.
Thread.current.abort_on_exception = true
activates this behavior
the current thread.
If we want this behavior globally for all the
threads then we need to use
Thread.abort_on_exception = true.
Running program with debug flag to stop on failure
Let’s run the original code with
In this case the exception is printed in detail and the code in main thread was not executed.
Usually when we execute a program with
then the behavior of the program does not change.
We expect the program to print more stuff but we do not
expect behavior to change. However in this case the
option changes the behavior of the program.
Running program with join on thread to stop on failure
If a thread raises an exception
$DEBUG flags are not set
that exception will be processed
at the time of joining of the thread.
will stop processing in the thread and in the main thread
once an exception is encountered.
Introduction of report_on_exception in Ruby 2.4
Almost 6 years ago, Charles Nutter (headius) had proposed that the exceptions raised in threads should be automatically logged and reported, by default. To make his point, he explained issues similar to what we discussed above about the Ruby’s behavior of silently ignoring exceptions in threads. Here is the relevant discussion on his proposal.
Following are some of the notable points discussed.
Thread[.#]abort_on_exception, by default, is not always a good idea.
- There should be a flag which, if enabled, would print the thread-killing exception info.
- In many cases, people spawn one-off threads which are not
Thread#value. Such threads gets garbage collected. Should it report the thread-killing exception at the time of garbage collection if such a flag is enabled?
- Should it warn using
Warning#warnor redirect to STDERR device while reporting?
Charles Nutter suggested that
a configurable global flag
and instance-level flag
should be implemented having its default value as
When set to
it should report print exception
Matz and other core members approved that
Thread[.#]report_on_exception can be implemented
having its default value set to
Charles Nutter, Benoit Daloze and other people demanded that
it should be
true by default so that programmers
can be aware of the silently disappearing threads
because of exceptions.
Shyouhei Urabe advised that
due to some technical challenges,
the default value should be set to
so as this feature could land in Ruby.
Once this feature is in then the default value can be changed in a later release.
Let’s try enabling
It now reports the exceptions in all threads.
It prints that the
terminated with exception: divided by 0 (ZeroDivisionError).
Similarly, another thread
terminated with exception: undefined method '+' for nil:NilClass (NoMethodError).
Instead of enabling it globally for all threads,
we can enable it for a particular thread
In the above case
we have enabled
report_on_exception flag just for
Let’s execute it.
Notice how it didn’t report the exception
which killed thread
As expected, it reported the exception
that killed thread
With the above changes ruby reports the exception as soon as it encounters. However if these threads are joined then they will still raise exception.
See how we were still be able
to handle the exception raised
division_thread above after joining it
despite it reported it before
report_on_exception defaults to true in Ruby 2.5
It was released as part of Ruby 2.5.
Now in ruby 2.5 we can simply write like this.
Let’s execute it with Ruby 2.5.
We can disable the thread exception reporting
Thread.report_on_exception = false
or for a
particular thread using
Thread.current.report_on_exception = false.
In addition to this feature, Charles Nutter also suggested that it will be good if there exists a callback handler which can accept a block to be executed when a thread dies due to an exception. The callback handler can be at global level or it can be for a specific thread.
In the absence of such handler libraries need to resort to custom code to handle exceptions. Here is how Sidekiq handles exceptions raised in threads.
Important thing to note is that
report_on_exception does not change behavior of the code.
It does more reporting when a thread dies and when it comes to thread dies more reporting
is a good thing.