The somewhat peculiar behavior of Ruby's Thread#raise

Here's something that's perhaps not entirely obvious: when you call Thread#raise, the exception will be raised at whatever point of execution that thread happens to be at.

require 'thread'

t = Thread.new{
  sleep 0.1
  sleep 0.1
  sleep 0.1
  sleep 0.1
  sleep 0.1
}

sleep rand(4) * 0.1

t.raise
t.join

output

➔ ruby thread_raise.rb
thread_raise.rb:6:in `sleep': unhandled exception
from thread_raise.rb:6:in `block in <main>'
➔ ruby thread_raise.rb
thread_raise.rb:7:in `sleep': unhandled exception
from thread_raise.rb:7:in `block in <main>'
➔ ruby thread_raise.rb
thread_raise.rb:5:in `sleep': unhandled exception
from thread_raise.rb:5:in `block in <main>'
➔ ruby thread_raise.rb
thread_raise.rb:5:in `sleep': unhandled exception
from thread_raise.rb:5:in `block in <main>'

See how the stack trace in the exception shows a different line of code each time? It's understandable that it would behave like this -- I can't think of any other behavior that would make more sense. But it should only be used when the code inside the thread knows that it's being called from a particular outer context, and that the outer context might have its own problems that it then tells the thread about. Example:

def do_jobs
  begin
    while true
      # do interesting things
    end
  rescue NoMoreDiskSpace
    # tidy things up
  end
end

t = Thread.new(do_jobs)

while true
  if disk_is_full
    t.raise(NoMoreDiskSpace)
  end
end

Unfortunately, Ruby's timeout library uses Thread#raise, even though it is frequently used around enormous amounts of arbitrary code and libraries that might have no idea that they are being called within Timeout#timeout and can't be expected to elegantly handle a timeout event at every single point in the code. Furthermore, this inner code might have blocks that call top-level Exception, which will then catch the exception that Timeout sends, handle it in a way it wasn't written for, and not raise it back up to the Timeout.timeout invocation. I'll elaborate on this issue more in future posts.

John BachirCo-Founder and CTO of Medstro

comments powered by Disqus