]> git.saurik.com Git - redis.git/blame - client-libraries/ruby/lib/better_timeout.rb
Ruby client library updated. Important changes in this new version!
[redis.git] / client-libraries / ruby / lib / better_timeout.rb
CommitLineData
ed9b544e 1#--
2# = timeout.rb
3#
4# execution timeout
5#
6# = Copyright
7#
8# Copyright - (C) 2008 Evan Phoenix
9# Copyright:: (C) 2000 Network Applied Communication Laboratory, Inc.
10# Copyright:: (C) 2000 Information-technology Promotion Agency, Japan
11#
12#++
13#
14# = Description
15#
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.
19#
20# Previous versions of timeout didn't use a module for namespace. This version
21# provides both Timeout.timeout, and a backwards-compatible #timeout.
22#
23# = Synopsis
24#
25# require 'timeout'
26# status = Timeout::timeout(5) {
27# # Something that should be interrupted if it takes too much time...
28# }
29#
30
31require 'thread'
32
33module Timeout
34
35 ##
36 # Raised by Timeout#timeout when the block times out.
37
38 class Error<Interrupt
39 end
40
41 # A mutex to protect @requests
42 @mutex = Mutex.new
43
44 # All the outstanding TimeoutRequests
45 @requests = []
46
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
55
56 attr_reader :thread, :left
57
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
64
65 # Raise @exception if @thread.
66 def cancel
67 if @thread and @thread.alive?
68 @thread.raise @exception, "execution expired"
69 end
70
71 @left = 0
72 end
73
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
81
82 def self.add_timeout(time, exc)
83
84 @controller ||= Thread.new do
85 while true
86 if @requests.empty?
87 sleep
88 next
89 end
90
91 min = nil
92
93 @mutex.synchronize do
94 min = @requests.min { |a,b| a.left <=> b.left }
95 end
96
97 slept_for = sleep(min.left)
98
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
109
110 end
111 end
112
113 req = TimeoutRequest.new(time, Thread.current, exc)
114
115 @mutex.synchronize do
116 @requests << req
117 end
118
119 @controller.run
120
121 return req
122 end
123
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
136
137 req = Timeout.add_timeout sec, exception
138
139 begin
140 yield sec
141 ensure
142 req.abort
143 end
144 end
145
146 module_function :timeout
147
148end
149
150##
151# Identical to:
152#
153# Timeout::timeout(n, e, &block).
154#
155# Defined for backwards compatibility with earlier versions of timeout.rb, see
156# Timeout#timeout.
157
158def timeout(n, e=Timeout::Error, &block) # :nodoc:
159 Timeout::timeout(n, e, &block)
160end
161
162##
163# Another name for Timeout::Error, defined for backwards compatibility with
164# earlier versions of timeout.rb.
165
29fac617 166class Object
167 remove_const(:TimeoutError) if const_defined?(:TimeoutError)
168end
ed9b544e 169TimeoutError = Timeout::Error # :nodoc:
170
171if __FILE__ == $0
172 p timeout(5) {
173 45
174 }
175 p timeout(5, TimeoutError) {
176 45
177 }
178 p timeout(nil) {
179 54
180 }
181 p timeout(0) {
182 54
183 }
184 p timeout(5) {
185 loop {
186 p 10
187 sleep 1
188 }
189 }
190end
191