The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
Care and Feeding of Timeout.timeout

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Eric Hodel

Posts: 660
Nickname: drbrain
Registered: Mar, 2006

Eric Hodel is a long-time Rubyist and co-founder of Seattle.rb.
Care and Feeding of Timeout.timeout Posted: Apr 11, 2006 11:09 AM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by Eric Hodel.
Original Post: Care and Feeding of Timeout.timeout
Feed Title: Segment7
Feed URL: http://blog.segment7.net/articles.rss
Feed Description: Posts about and around Ruby, MetaRuby, ruby2c, ZenTest and work at The Robot Co-op.
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by Eric Hodel
Latest Posts From Segment7

Advertisement

If you’re not careful when using Timeout.timeout you can end up with some hard to find bugs.

The first is that nesting timeouts without different timeout exception classes is very bad. Let’s say you have a process that can connect to multiple servers, but you want to give up and try the next server if it takes too long. You’d probably write something like this:

require 'timeout'

servers = [1, 2]
current_server = 0

begin
  Timeout.timeout 2 do
    puts "Connecting to server #{servers[current_server]}" 
    sleep # simulate work
  end
rescue Timeout::Error
  puts "Failed" 
  current_server += 1
  retry unless current_server == servers.length
end

This is sensible code, if the work takes to long you’ll fail and move on to the next server.

But now its been a few months and you’ve added servers, but you want your application to only try for so many seconds then give up completely. Your real app would be properly factored (of course) so the bug with the simple solution wouldn’t necessarily be obvious:

require 'timeout'

servers = [1, 2]
current_server = 0

Timeout.timeout 5 do
  puts 'Setting up some stuff'
  sleep 4
  begin
    Timeout.timeout 2 do
      puts "Connecting to server #{servers[current_server]}" 
      sleep
    end
  rescue Timeout::Error
    puts "Failed." 
    current_server += 1
    retry unless current_server == servers.length
  end
end

When we run this code we run longer than we were supposed to:

<samp>$ time ruby t.rb
Setting up some stuff
Connecting to server 1
Failed.
Connecting to server 2
Failed.

real    0m7.044s
user    0m0.014s
sys     0m0.011s

Why seven seconds instead of five? The inner timeout block caught the out timeout block’s exception and continued doing what it was doing. This isn’t what we want, but Timeout.timeout allows you to change the raised exception:

require 'timeout'

servers = [1, 2]
current_server = 0

class ServerTimeout < Timeout::Error; end
class AppTimeout < Timeout::Error; end

Timeout.timeout 5, AppTimeout do
  puts 'Setting up some stuff'
  sleep 4
  begin
    Timeout.timeout 2, ServerTimeout do
      puts "Connecting to server #{servers[current_server]}" 
      sleep
    end
  rescue ServerTimeout
    puts "Failed." 
    current_server += 1
    retry unless current_server == servers.length
  end
end

So now the outer timeout can stop execution even from inside the inner timeout:

<samp>$ time ruby t.rb
Setting up some stuff
Connecting to server 1
/usr/local/lib/ruby/1.8/timeout.rb:54: execution expired (AppTimeout)
        from /usr/local/lib/ruby/1.8/timeout.rb:56:in `timeout'
        from t.rb:13
        from /usr/local/lib/ruby/1.8/timeout.rb:56:in `timeout'
        from t.rb:9

real    0m5.069s
user    0m0.022s
sys     0m0.015s</samp>

The second to watch out for is Timeout killing your rescue or ensure blocks. A timeout raised inside an ensure block will stop execution, so for critical ensure blocks you should wrap them in their own begin/end block:

require 'timeout'

Timeout.timeout 2 do
  begin
    puts "Allocating the thingy..." 
    sleep 1
    raise RuntimeError, 'Oh no! Something went wrong!'
  ensure
    # Since we might time out, hold onto the timeout we caught
    # so we can re-raise it when we're done cleaning up.
    timeout = nil
    begin # we really need to clean up
      puts "Cleaning up after the thingy..." 
      sleep 2
      puts "Cleaned up after the thingy!" 
    rescue Timeout::Error => e
      puts "Timed out! Trying again!" 
      timeout = e # save that timeout then retry
      retry
    end
    # Raise the timeout so we time out all the way to the top.
    raise timeout unless timeout.nil?
  end
end

Read: Care and Feeding of Timeout.timeout

Topic: Como NO escribir manejo de excepciones en JAVA Previous Topic   Next Topic Topic: The High Maintenance Ruby Mainstream

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use