Ruby’s Timeout

If you think you’ve been around the block a few times and know your ins-and-outs of Ruby’s funkiest details, here’s a quick Ruby quiz for you: on MRI, what does this piece of code print out?

require 'timeout'

def do_stuff
  sleep(2)
  puts "done sleeping"
rescue => e
  puts 'boom!'
ensure
  puts 'all done'
end

begin
  Timeout::timeout(1) do
    do_stuff
  end
rescue Timeout::Error
  puts 'timed out'
end

If you answered timed out, that’s not a bad start. After all, the #timeout method is what raises the error, right? Or… no, actually. Ruby’s timeout method actually works by kicking off a new thread, which sleeps for the timeout duration and then raises an exception on the original execution thread, at whatever point in the code it happens to be at. The stack trace will read as if it came from a random line in your code (which can be surprising to the uninitiated).

Okay, so it prints out boom! then all done, right? Nope. The rescue block in #do_stuff isn’t able to rescue the timeout error, because – contrary to what the docs imply – the timeout exception actually raises a Timeout::ExitException, which is not a StandardError and thus is not rescuable without rescuing Exception.

So although the rescue doesn’t catch the exception, the ensure block will still trigger. Which means the correct answer to my quiz is…

all done
timed out

But wait, there’s more! #

Let’s mix things up a bit. Given what I’ve explained, see if you can guess what this prints out:

require 'timeout'

def do_stuff
  sleep(2)
  puts "done sleeping"
rescue => e
  puts 'boom!'
ensure
  puts 'all done'
  return 1                      # This is the only addition!
end

begin
  Timeout::timeout(1) do
    do_stuff
  end
rescue Timeout::Error
  puts 'timed out'
end

If you’re at a loss to how this could be different, take a second to think about what it even means to have an ensure block return a value. An exception should halt execution, right? But returning a value means execution continues. Sounds like a paradox to me, and it’s one Ruby resolves by… dropping the exception on the floor (see Les Hill’s post on the topic for more detail). So what actually prints out is just:

all done

And you have no idea anything even timed out, except that whatever you wanted to happen probably didn’t happen.

Doing things sanely #

Timeouts are useful. But if you want to avoid unnecessary headaches with them, here are a few tips:

 
2
Kudos
 
2
Kudos

Now read this

The Case of the Missing Commits (or, The Dangers of Git Forced Updates)

It was a cold Wednesday afternoon when I got the email. Another developer’s mundane bug fixes deployed to production. Nothing special. But an alarmed business owner’s reply caught my eye: “Did this release overwrite our updates to the... Continue →