Fix up Rubinius specific library specs.
[rbx.git] / lib / timeout.rb
blobac12c6ebe1a0e1b7df98d0e5741c4cee4d5dae22
1 #--
2 # = timeout.rb
4 # execution timeout
6 # = Copyright
8 # Copyright - (C) 2008  Evan Phoenix
9 # Copyright:: (C) 2000  Network Applied Communication Laboratory, Inc.
10 # Copyright:: (C) 2000  Information-technology Promotion Agency, Japan
12 #++
14 # = Description
16 # A way of performing a potentially long-running operation in a thread, and
17 # terminating it's execution if it hasn't finished within fixed amount of
18 # time.
20 # Previous versions of timeout didn't use a module for namespace. This version
21 # provides both Timeout.timeout, and a backwards-compatible #timeout.
23 # = Synopsis
25 #   require 'timeout'
26 #   status = Timeout::timeout(5) {
27 #     # Something that should be interrupted if it takes too much time...
28 #   }
31 require 'thread'
33 module Timeout
35   ##
36   # Raised by Timeout#timeout when the block times out.
38   class Error<Interrupt
39   end
41   # A mutex to protect @requests
42   @mutex = Mutex.new
44   # All the outstanding TimeoutRequests
45   @requests = []
47   # Represents +thr+ asking for it to be timeout at in +secs+
48   # seconds. At timeout, raise +exc+.
49   class TimeoutRequest
50     def initialize(secs, thr, exc)
51       @left = secs
52       @thread = thr
53       @exception = exc
54     end
56     attr_reader :thread, :left
58     # Called because +time+ seconds have gone by. Returns
59     # true if the request has no more time left to run.
60     def elapsed(time)
61       @left -= time
62       @left <= 0
63     end
65     # Raise @exception if @thread.
66     def cancel
67       if @thread and @thread.alive?
68         @thread.raise @exception, "execution expired"
69       end
71       @left = 0
72     end
74     # Abort this request, ie, we don't care about tracking
75     # the thread anymore.
76     def abort
77       @thread = nil
78       @left = 0
79     end
80   end
82   def self.add_timeout(time, exc)
84     @controller ||= Thread.new do
85       while true
86         if @requests.empty?
87           sleep
88           next
89         end
91         min = nil
93         @mutex.synchronize do
94           min = @requests.min { |a,b| a.left <=> b.left }
95         end
97         slept_for = sleep(min.left)
99         @mutex.synchronize do
100           @requests.delete_if do |r|
101             if r.elapsed(slept_for)
102               r.cancel
103               true
104             else
105               false
106             end
107           end
108         end
110       end
111     end
113     req = TimeoutRequest.new(time, Thread.current, exc)
115     @mutex.synchronize do
116       @requests << req
117     end
119     @controller.run
121     return req
122   end
124   ##
125   # Executes the method's block. If the block execution terminates before +sec+
126   # seconds has passed, it returns true. If not, it terminates the execution
127   # and raises +exception+ (which defaults to Timeout::Error).
128   #
129   # Note that this is both a method of module Timeout, so you can 'include
130   # Timeout' into your classes so they have a #timeout method, as well as a
131   # module method, so you can call it directly as Timeout.timeout().
132   
133   def timeout(sec, exception=Error)
134     return yield if sec == nil or sec.zero?
135     raise ThreadError, "timeout within critical session" if Thread.critical
137     req = Timeout.add_timeout sec, exception
138     
139     begin
140       yield sec
141     ensure
142       req.abort
143     end
144   end
146   module_function :timeout
151 # Identical to:
153 #   Timeout::timeout(n, e, &block).
155 # Defined for backwards compatibility with earlier versions of timeout.rb, see
156 # Timeout#timeout.
158 def timeout(n, e=Timeout::Error, &block) # :nodoc:
159   Timeout::timeout(n, e, &block)
163 # Another name for Timeout::Error, defined for backwards compatibility with
164 # earlier versions of timeout.rb.
166 TimeoutError = Timeout::Error # :nodoc:
168 if __FILE__ == $0
169   p timeout(5) {
170     45
171   }
172   p timeout(5, TimeoutError) {
173     45
174   }
175   p timeout(nil) {
176     54
177   }
178   p timeout(0) {
179     54
180   }
181   p timeout(5) {
182     loop {
183       p 10
184       sleep 1
185     }
186   }