1# Timeout long-running blocks
2#
3# == Synopsis
4#
5#   require 'timeout'
6#   status = Timeout::timeout(5) {
7#     # Something that should be interrupted if it takes more than 5 seconds...
8#   }
9#
10# == Description
11#
12# Timeout provides a way to auto-terminate a potentially long-running
13# operation if it hasn't finished in a fixed amount of time.
14#
15# Previous versions didn't use a module for namespacing, however
16# #timeout is provided for backwards compatibility.  You
17# should prefer Timeout#timeout instead.
18#
19# == Copyright
20#
21# Copyright:: (C) 2000  Network Applied Communication Laboratory, Inc.
22# Copyright:: (C) 2000  Information-technology Promotion Agency, Japan
23
24module Timeout
25  # Raised by Timeout#timeout when the block times out.
26  class Error < RuntimeError
27  end
28  class ExitException < ::Exception # :nodoc:
29  end
30
31  # :stopdoc:
32  THIS_FILE = /\A#{Regexp.quote(__FILE__)}:/o
33  CALLER_OFFSET = ((c = caller[0]) && THIS_FILE =~ c) ? 1 : 0
34  # :startdoc:
35
36  # Perform an operation in a block, raising an error if it takes longer than
37  # +sec+ seconds to complete.
38  #
39  # +sec+:: Number of seconds to wait for the block to terminate. Any number
40  #         may be used, including Floats to specify fractional seconds. A
41  #         value of 0 or +nil+ will execute the block without any timeout.
42  # +klass+:: Exception Class to raise if the block fails to terminate
43  #           in +sec+ seconds.  Omitting will use the default, Timeout::Error
44  #
45  # Returns the result of the block *if* the block completed before
46  # +sec+ seconds, otherwise throws an exception, based on the value of +klass+.
47  #
48  # Note that this is both a method of module Timeout, so you can <tt>include
49  # Timeout</tt> into your classes so they have a #timeout method, as well as
50  # a module method, so you can call it directly as Timeout.timeout().
51  def timeout(sec, klass = nil)   #:yield: +sec+
52    return yield(sec) if sec == nil or sec.zero?
53    exception = klass || Class.new(ExitException)
54    begin
55      begin
56        x = Thread.current
57        y = Thread.start {
58          begin
59            sleep sec
60          rescue => e
61            x.raise e
62          else
63            x.raise exception, "execution expired"
64          end
65        }
66        return yield(sec)
67      ensure
68        if y
69          y.kill
70          y.join # make sure y is dead.
71        end
72      end
73    rescue exception => e
74      rej = /\A#{Regexp.quote(__FILE__)}:#{__LINE__-4}\z/o
75      (bt = e.backtrace).reject! {|m| rej =~ m}
76      level = -caller(CALLER_OFFSET).size
77      while THIS_FILE =~ bt[level]
78        bt.delete_at(level)
79        level += 1
80      end
81      raise if klass            # if exception class is specified, it
82                                # would be expected outside.
83      raise Error, e.message, e.backtrace
84    end
85  end
86
87  module_function :timeout
88end
89
90# Identical to:
91#
92#   Timeout::timeout(n, e, &block).
93#
94# This method is deprecated and provided only for backwards compatibility.
95# You should use Timeout#timeout instead.
96def timeout(n, e = nil, &block)
97  Timeout::timeout(n, e, &block)
98end
99
100# Another name for Timeout::Error, defined for backwards compatibility with
101# earlier versions of timeout.rb.
102TimeoutError = Timeout::Error
103