---
title: "Ruby 2.5 adds Thread.report_on_exception by default"
description:
  "Ruby 2.5 enables Thread.report_on_exception by default and we get more info
  when threads die"
canonical_url: "https://www.bigbinary.com/blog/ruby-2-5-enables-thread-report_on_exception-by-default"
markdown_url: "https://www.bigbinary.com/blog/ruby-2-5-enables-thread-report_on_exception-by-default.md"
---

# Ruby 2.5 adds Thread.report_on_exception by default

Ruby 2.5 enables Thread.report_on_exception by default and we get more info when
threads die

- Author: Vishal Telangre
- Published: April 18, 2018
- Categories: Ruby 2.5, Ruby

Let's see what happens when an exception is raised inside a thread.

```ruby
division_thread = Thread.new do
  puts "Calculating 4/0 in division_thread"
  puts "Result is: #{4/0}"
  puts "Exiting from division_thread"
end

sleep 1

puts "In the main thread"
```

Execution of it looks like this.

```ruby
$ RBENV_VERSION=2.4.0 ruby thread_example_1.rb

Calculating 4/0 in division_thread

In the main thread
```

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 `Thread.current`.

```ruby
division_thread = Thread.new do
  Thread.current.abort_on_exception = true

  puts "Calculating 4/0 in division_thread"
  puts "Result is: #{4/0}"
  puts "Exiting from division_thread"
end

sleep 1

puts "In the main thread"
```

```ruby
$ RBENV_VERSION=2.4.0 ruby thread_example_2.rb

Calculating 4/0 in division_thread

thread_example_2.rb:5:in `/': divided by 0 (ZeroDivisionError)
  from thread_example_2.rb:5:in `block in <main>'
```

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.

Note that `Thread.current.abort_on_exception = true` activates this behavior
only for 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 `--debug` option.

```ruby
$ RBENV_VERSION=2.4.0 ruby --debug thread_example_1.rb

thread_example_1.rb:1: warning: assigned but unused variable - division_thread

Calculating 4/0 in division_thread

Exception `ZeroDivisionError' at thread_example_1.rb:3 - divided by 0
Exception `ZeroDivisionError' at thread_example_1.rb:7 - divided by 0
thread_example_1.rb:3:in `/': divided by 0 (ZeroDivisionError)
  from thread_example_1.rb:3:in `block in <main>'
```

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 `--debug` option 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 `--debug` option changes the
behavior of the program.

## Running program with join on thread to stop on failure

If a thread raises an exception and `abort_on_exception` and `$DEBUG` flags are
not set then that exception will be processed at the time of joining of the
thread.

```ruby
division_thread = Thread.new do
  puts "Calculating 4/0 in division_thread"
  puts "Result is: #{4/0}"
  puts "Exiting from division_thread"
end

division_thread.join

puts "In the main thread"
```

```ruby
$ RBENV_VERSION=2.4.0 ruby thread_example_3.rb

Calculating 4/0 in division_thread

thread_example_3.rb:3:in `/': divided by 0 (ZeroDivisionError)
  from thread_example_3.rb:3:in `block in <main>'
```

Both `Thread#join` and `Thread#value` 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)](https://github.com/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](https://bugs.ruby-lang.org/issues/6647) is the relevant
discussion on his proposal.

Following are some of the notable points discussed.

- Enabling `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 hard-referenced
  using `Thread#join` or `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#warn`](https://ruby-doc.org/core-2.4.0/Warning.html#method-i-warn)
  or redirect to STDERR device while reporting?

Charles Nutter suggested that a configurable global flag
`Thread.report_on_exception` and instance-level flag
`Thread#report_on_exception` should be implemented having its default value as
`true`. When set to `true`, it should report print exception information.

Matz and other core members approved that `Thread[.#]report_on_exception` can be
implemented having its default value set to `false`.

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](https://bugs.ruby-lang.org/issues/6647#note-41) that
due to some technical challenges, the default value should be set to `false` so
as this feature could land in Ruby. Once this feature is in then the default
value can be changed in a later release.

[Nobuyoshi Nakada (nobu)](https://github.com/nobu) pushed an
[implementation](https://github.com/ruby/ruby/commit/2e71c752787e0c7659bd5e89b6c5d433eddfe13a)
for `Thread[.#]report_on_exception` with a default value set to `false`. It was
released in Ruby 2.4.0.

Let's try enabling `report_on_exception` globally using
`Thread.report_on_exception`.

```ruby
Thread.report_on_exception = true

division_thread = Thread.new do
  puts "Calculating 4/0 in division_thread"
  puts "Result is: #{4/0}"
  puts "Exiting from division_thread"
end

addition_thread = Thread.new do
  puts "Calculating nil+4 in addition_thread"
  puts "Result is: #{nil+4}"
  puts "Exiting from addition_thread"
end

sleep 1

puts "In the main thread"
```

```ruby
$ RBENV_VERSION=2.4.0 ruby thread_example_4.rb

Calculating 4/0 in division_thread

#<Thread:0x007fb10f018200@thread_example_4.rb:3 run> terminated with exception:
thread_example_4.rb:5:in `/': divided by 0 (ZeroDivisionError)
  from thread_example_4.rb:5:in `block in <main>'

Calculating nil+4 in addition_thread

#<Thread:0x007fb10f01aca8@thread_example_4.rb:9 run> terminated with exception:
thread_example_4.rb:11:in `block in <main>': undefined method `+' for nil:NilClass (NoMethodError)

In the main thread
```

It now reports the exceptions in all threads. It prints that the
`Thread:0x007fb10f018200` was
`terminated with exception: divided by 0 (ZeroDivisionError)`. Similarly,
another thread `Thread:0x007fb10f01aca8` was
`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 using instance-level `Thread#report_on_exception`.

```ruby
division_thread = Thread.new do
  puts "Calculating 4/0 in division_thread"
  puts "Result is: #{4/0}"
  puts "Exiting from division_thread"
end

addition_thread = Thread.new do
  Thread.current.report_on_exception = true

  puts "Calculating nil+4 in addition_thread"
  puts "Result is: #{nil+4}"
  puts "Exiting from addition_thread"
end

sleep 1

puts "In the main thread"
```

In the above case we have enabled `report_on_exception` flag just for
`addition_thread`.

Let's execute it.

```ruby
$ RBENV_VERSION=2.4.0 ruby thread_example_5.rb

Calculating 4/0 in division_thread

Calculating nil+4 in addition_thread

#<Thread:0x007f8e6b007f70@thread_example_5.rb:7 run> terminated with exception:
thread_example_5.rb:11:in `block in <main>': undefined method `+' for nil:NilClass (NoMethodError)

In the main thread
```

Notice how it didn't report the exception which killed thread `division_thread`.
As expected, it reported the exception that killed thread `addition_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.

```ruby
division_thread = Thread.new do
  Thread.current.report_on_exception = true

  puts "Calculating 4/0 in division_thread"
  puts "Result is: #{4/0}"
  puts "Exiting from division_thread"
end

begin
  division_thread.join
rescue => exception
  puts "Explicitly caught - #{exception.class}: #{exception.message}"
end

puts "In the main thread"
```

```ruby
$ RBENV_VERSION=2.4.0 ruby thread_example_6.rb

Calculating 4/0 in division_thread

#<Thread:0x007f969d00d828@thread_example_6.rb:1 run> terminated with exception:
thread_example_6.rb:5:in `/': divided by 0 (ZeroDivisionError)
  from thread_example_6.rb:5:in `block in <main>'

Explicitly caught - ZeroDivisionError: divided by 0

In the main thread
```

See how we were still be able to handle the exception raised in
`division_thread` above after joining it despite it reported it before due to
`Thread#report_on_exception` flag.

## report_on_exception defaults to true in Ruby 2.5

[Benoit Daloze (eregon)](https://github.com/eregon) strongly advocated that both
the `Thread.report_on_exception` and `Thread#report_on_exception` should have
default value as `true`. [Here](https://bugs.ruby-lang.org/issues/14143) is the
relevant feature request.

After [approval from Matz](https://bugs.ruby-lang.org/issues/14143#note-9),
Benoit Daloze pushed the
[implementation](https://github.com/ruby/ruby/search?utf8=%E2%9C%93&q=Feature+%5C%2314143&type=Commits)
by fixing the failing tests and silencing the unnecessary verbose warnings.

It was released as part of Ruby 2.5.

Now in ruby 2.5 we can simply write like this.

```ruby
division_thread = Thread.new do
  puts "Calculating 4/0 in division_thread"
  puts "Result is: #{4/0}"
  puts "Exiting from division_thread"
end

addition_thread = Thread.new do
  puts "Calculating nil+4 in addition_thread"
  puts "Result is: #{nil+4}"
  puts "Exiting from addition_thread"
end

sleep 1

puts "In the main thread"
```

Let's execute it with Ruby 2.5.

```ruby
$ RBENV_VERSION=2.5.0 ruby thread_example_7.rb

Calculating 4/0 in division_thread

#<Thread:0x00007f827689a238@thread_example_7.rb:1 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
  1: from thread_example_7.rb:3:in `block in <main>'
thread_example_7.rb:3:in `/': divided by 0 (ZeroDivisionError)

Calculating nil+4 in addition_thread

#<Thread:0x00007f8276899b58@thread_example_7.rb:7 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
thread_example_7.rb:9:in `block in <main>': undefined method `+' for nil:NilClass (NoMethodError)

In the main thread
```

We can disable the thread exception reporting globally using
`Thread.report_on_exception = false` or for a particular thread using
`Thread.current.report_on_exception = false`.

## Future Possibilities

In addition to this feature, Charles Nutter also
[suggested](https://bugs.ruby-lang.org/issues/14143#note-4) 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.

```ruby
Thread.on_exception do
  # some stuff
end
```

In the absence of such handler libraries need to resort to custom code to handle
exceptions.
[Here is how](https://github.com/mperham/sidekiq/blob/a60a91d3dd857592a532965f0701d285f13f28f1/lib/sidekiq/util.rb#L15-L27)
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.

## Links

- [Human page](https://www.bigbinary.com/blog/ruby-2-5-enables-thread-report_on_exception-by-default)
