ruby Thread report_on_exception and abort_on_exception
ruby Thread
has two interesting settings related to behavior when an error happens: report_on_exception
and abort_on_exception
.
There are a few good articles out there about this, but a couple things gets overlooked a bit:
- behavior which depends on if a thread is
join
ed or not - the interaction of the two settings possibly causing confusion
I'll elaborate more on these in the sections below.
The basics
These things apply to both settings:
- They can be set globally on
Thread
or on an instance ofThread
- If an unhandled error is raised in a
Thread
, this will haltThread
execution
abort_on_exception (default false)
abort_on_exception
has been around for a while, I'm not sure since when.
When false, if a Thread
raises an error, the error will not be raised until join
is called on the inner thread. If join
is never called, the error is never raised.
Here, the error is never raised, and the last line is reached:
Thread.abort_on_exception=false
puts "starting main thread"
t= Thread.new{ raise "hello" }
sleep 0.1
puts "thread is running"
sleep 1
puts "ending main thread"
Here, the error is raised when the thread is join
ed, and the last line is never reached:
Thread.abort_on_exception=false
puts "starting main thread"
t= Thread.new{ raise "hello" }
sleep 0.1
puts "thread is running"
t.join
sleep 1
puts "ending main thread"
Here, the error is raised immediately,"thread is running" is never printed, and of course the last line is never reached:
Thread.abort_on_exception=true
puts "starting main thread"
t= Thread.new{ raise "hello" }
sleep 0.1
puts "thread is running"
t.join # irrelevant
sleep 1
puts "ending main thread"
report_on_exception (default true)
report_on_exception
has been available since 2.4, and has had a default of true since 2.5. You can read about the nitty gritty of the history of this setting here.
The behavior here is pretty straightforward. When true
, errors in threads will be immediately printed to STDERR. The ruby team setting this to true
by default is pretty reasonable, it's good to not have things silently failing, at least a log is showing up somewhere. But keep in mind that in the default configuration, you will then see the same identical log printed twice when the Thread
is joined:
Thread.report_on_exception=true # default
Thread.abort_on_exception=false # default
puts "starting main thread"
t= Thread.new{ raise "hello" }
sleep 0.1
puts "thread is running"
t.join
sleep 1
puts "ending main thread"
results in...
starting main thread
#<Thread:0x00000001368544e0 thread.rb:5 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
thread.rb:5:in `block in <main>': hello (RuntimeError)
thread is running
Traceback (most recent call last):
thread.rb:5:in `block in <main>': hello (RuntimeError)
A pretty minor problem, but this can be really annoying and vaguely concerning for someone who doesn't know what's going on. I've encountered this before and wondered, was I actually creating 2 threads when I thought I was creating only 1?
In most cases, this will be inconsequential and there's probably no need to change the defaults. But in a scenario where you are changing abort_on_exception
to true, I'd say you probably always want to also set report_on_exception
to false.