Getting to Know the Ruby Standard Library – Timeout
I
asked for suggestions about what to cover next, and
postmodern suggested the
Timeout
library among others.
Timeout
lets you run a block of code, and ensure it takes no longer than a specified amount of time. The most common use case is for operations that rely on a third party, for instance
net/http
uses it to make sure that your script does not wait forever while trying to connect to a server:
def connect
#...
timeout(@open_timeout) { TCPSocket.open(conn_address(), conn_port()) }
#...
You could also use
Timeout
to ensure that processing a file uploaded by a user does not take too long. For instance if you allow people to upload files to your server, you might want to limit reject any files that take more than 2 seconds to parse:
require 'csv'
def read_csv(path)
begin
timeout(2){ CSV.read(path) }
rescue Timeout::Error => ex
puts "File '#{path}' took too long to parse."
return nil
end
end
Lets take a look at how it works. Open up the
Timeout
library, you can use
qw timeout
if you have
Qwandry installed. Peek at the
timeout
method, it is surprisingly short.
def timeout(sec, klass = nil) #:yield: +sec+
return yield(sec) if sec == nil or sec.zero?
#...
First of all, we can see that if
sec
is either
0
or
nil
it just executes the block you passed in, and then returns the result. Next lets look at the part of
Timeout
that actually does the timing out:
#...
x = Thread.current
y = Thread.start {
sleep sec
x.raise exception, "execution expired" if x.alive?
}
return yield(sec)
#...
We quickly see the secret here is in ruby’s threads. If you’re not familiar with threading, it is more or less one way to make the computer do two things at once. First
Timeout
stashes the current thread in
x
. Next it starts up a new thread that will sleep for your timeout period. The sleeping thread is stored in
y
. While that thread is sleeping, it calls the block passed into timeout. As soon as that block completes, the result is returned. So what about that sleeping thread? When it wakes up it will raise an exception, which explains the how
timeout
stops code from running forever, but there is one last piece to the puzzle.
#...
ensure
if y and y.alive?
y.kill
y.join # make sure y is dead.
end
end
#...
At the end of
timeout
there is an
ensure
. If you haven’t come across this yet, it is an interesting feature in ruby.
ensure
will
always
be called after a method completes, even if there is an exception. In
timeout
the
ensure
kills thread
y
, the sleeping thread, which means that it won’t raise an exception if the block returns, or throws an exception before the thread wakes up.
It turns out that
Timeout
is a useful little library, and it contains some interesting examples of threading and
ensure
blocks. If there is any part of the standard library you are curious about or think is worthy of some more coverage, let me know!
More articles in this series
- Getting to Know the Ruby Standard Library – Delegator
- Getting to Know the Ruby Standard Library – WeakRef
- Getting to Know the Ruby Standard Library – Timeout
- Getting to Know the Ruby Standard Library – Pathname
- Getting to Know the Ruby Standard Library – Abbrev
- Getting to Know the Ruby Standard Library – TSort
- Getting to Know the Ruby Standard Library – MiniTest::Mock
- Getting to Know the Ruby Standard Library – Shellwords
- Getting to Know the Ruby Standard Library – MiniTest