Flylib.com

Books Software

 
 
 

Recipe 3.14. Adding a Timeout to a Long-Running Operation


Recipe 3.14. Adding a Timeout to a Long-Running Operation

Problem

You're running some code that might take a long time to complete, or might never complete at all. You want to interrupt the code if it takes too long.

Solution

Use the built-in timeout library. The Timeout.timeout method takes a code block and a deadline (in seconds). If the code block finishes running in time, it returns true. If the deadline passes and the code block is still running, Timeout.timeout terminates the code block and raises an exception.

The following code would never finish running were it not for the timeout call. But after five seconds, timeout raises a Timeout::Error and execution halts:

# This code will sleep forever… OR WILL IT?
	require 'timeout'
	before = Time.now
	begin
	  status = Timeout.timeout(5) { sleep }
	rescue Timeout::Error
	  puts "I only slept for #{Time.now-before} seconds."
	end
	# I only slept for 5.035492 seconds.

Discussion

Sometimes you must make a network connection or take some other action that might be incredibly slow, or that might never complete at all. With a timeout, you can impose an upper limit on how long that operation can take. If it fails, you can try it again later, or forge ahead without the information you were trying to get. Even when you can't recover, you can report your failure and gracefully exit the program, rather than sitting around forever waiting for the operation to complete.

By default, Timeout.timeout raises a Timeout::Error . You can pass in a custom exception class as the second argument to Timeout.timeout : this saves you from having to rescue the Timeout:Error just so you can raise some other error that your application knows how to handle.

If the code block had side effects, they will still be visible after the timeout kills the code block:

def count_for_five_seconds
	  $counter = 0
	  begin
	    Timeout::timeout(5) { loop { $counter += 1 } }
	  rescue Timeout::Error
	    puts "I can count to #{$counter} in 5 seconds."
	  end
	end

	count_for_five_seconds
	# I can count to 2532825 in 5 seconds.
	$counter                                 # => 2532825

This may mean that your dataset is now in an inconsistent state.

See Also

  • ri Timeout

  • Recipe 3.13, "Waiting a Certain Amount of Time"

  • Recipe 14.1, "Grabbing the Contents of a Web Page"



Chapter 4. Arrays

Like all high-level languages, Ruby has built-in support for arrays , objects that contain ordered lists of other objects. You can use arrays (often in conjunction with hashes) to build and use complex data structures without having to define any custom classes.

An array in Ruby is an ordered list of elements . Each element is a reference to some object, the way a Ruby variable is a reference to some object. For convenience, throughout this book we usually talk about arrays as though the array elements were the actual objects, not references to the objects. Since Ruby (unlike languages like C) gives no way of manipulating object references directly, the distinction rarely matters.

The simplest way to create a new array is to put a comma-separated list of object references between square brackets. The object references can be predefined variables ( my_var ), anonymous objects created on the spot (' my string ', 4.7 , or MyClass.new ), or expressions ( a+b, object.method ). A single array can contain references to objects of many different types:

a1 = []                              # => []
	a2 = [1, 2, 3]                       # => [1, 2, 3]
	a3 = [1, 2, 3, 'a', 'b', 'c', nil]   # => [1, 2, 3, "a", "b", "c", nil]

	n1 = 4
	n2 = 6
	sum_and_difference = [n1, n2, n1+n2, n1-n2]
	# => [4, 6, 10, -2]

If your array contains only strings, you may find it simpler to build your array by enclosing the strings in the w{} syntax, separated by whitespace. This saves you from having to write all those quotes and comma:

%w{1 2 3}                            # => ["1", "2", "3"]
	%w{The rat sat
	   on the mat}
	# => ["The", "rat", "sat", "on", "the", "mat"]

The << operator is the simplest way to add a value to an array. Ruby dynamically resizes arrays as elements are added and removed.

a = [1, 2, 3]                  # => [1, 2, 3]
	a << 4.0                       # => [1, 2, 3, 4.0]
	a << 'five'                    # => [1, 2, 3, 4.0, "five"]

An array element can be any object reference, including a reference to another array. An array can even contain a reference to itself, though this is usually a bad idea, since it can send your code into infinite loops .

a = [1,2,3]                      # => [1, 2, 3]
	a << [4, 5, 6]                   # => [1, 2, 3, [4, 5, 6]]
	a << a                           # => [1, 2, 3, [4, 5, 6], […]]

As in most other programming languages, the elements of an array are numbered with indexes starting from zero. An array element can be looked up by passing its index into the array index operator [] . The first element of an array can be accessed with a[0] , the second with a[1] , and so on.

Negative indexes count from the end of the array: the last element of an array can be accessed with a[-1] , the second-to-last with a[-2] , and so on. See Recipe 4.13 for more ways of using the array indexing operator.

The size of an array is available through the Array#size method. Because the index numbering starts from zero, the index of the last element of an array is the size of the array, minus one.

a = [1, 2, 3, [4, 5, 6]]
	a.size                               # => 4
	a << a                               # => [1, 2, 3, [4, 5, 6], […]]
	a.size                               # => 5

	a[0]                                 # => 1
	a[3]                                 # => [4, 5, 6]
	a[3][0]                              # => 4
	a[3].size                            # => 3

	a[-2]                                # => [4, 5, 6]
	a[-1]                                # => [1, 2, 3, [4, 5, 6], […]]
	a[a.size-1]                          # => [1, 2, 3, [4, 5, 6], […]]

	a[-1][-1]                            # => [1, 2, 3, [4, 5, 6], […]]
	a[-1][-1][-1]                        # => [1, 2, 3, [4, 5, 6], […]]

All languages with arrays have constructs for iterating over them (even if it's just a for loop). Languages like Java and Python have general iterator methods similar to Ruby's, but they're usually used for iterating over arrays. In Ruby, iterators are the standard way of traversing all data structures: array iterators are just their simplest manifestation.

Ruby's array iterators deserve special study because they're Ruby's simplest and most accessible iterator methods. If you come to Ruby from another language, you'll probably start off thinking of iterator methods as letting you treat aspects of a data structure "like an array." Recipe 4.1 covers the basic array iterator methods, including ones in the Enumerable module that you'll encounter over and over again in different contexts.

The Set class, included in Ruby's standard library, is a useful alternative to the Array class for many basic algorithms. A Ruby set models a mathematical set: sets are not ordered, and cannot contain more than one reference to the same object. For more about sets, see Recipes 4.14 and 4.15.