frugal_timeout 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/frugal_timeout.rb +77 -45
- metadata +2 -18
data/lib/frugal_timeout.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'hitimes'
|
4
4
|
require 'monitor'
|
5
|
-
require 'null_object'
|
6
5
|
require 'thread'
|
7
6
|
require 'timeout'
|
8
7
|
|
@@ -55,11 +54,11 @@ module FrugalTimeout
|
|
55
54
|
include Comparable
|
56
55
|
@@mutex = Mutex.new
|
57
56
|
|
58
|
-
attr_reader :at, :
|
57
|
+
attr_reader :at, :klass, :thread
|
59
58
|
|
60
59
|
def initialize thread, at, klass
|
61
60
|
@thread, @at, @klass = thread, at, klass
|
62
|
-
@defused
|
61
|
+
@defused = false
|
63
62
|
end
|
64
63
|
|
65
64
|
def <=>(other)
|
@@ -75,12 +74,9 @@ module FrugalTimeout
|
|
75
74
|
@@mutex.synchronize { @defused }
|
76
75
|
end
|
77
76
|
|
78
|
-
def enforceTimeout
|
77
|
+
def enforceTimeout
|
79
78
|
@@mutex.synchronize {
|
80
|
-
|
81
|
-
|
82
|
-
filter[@thread] = true
|
83
|
-
@thread.raise @exception, 'execution expired'
|
79
|
+
@thread.raise @klass, 'execution expired' unless @defused
|
84
80
|
}
|
85
81
|
end
|
86
82
|
end
|
@@ -95,6 +91,11 @@ module FrugalTimeout
|
|
95
91
|
@onNewNearestRequest, @requests = proc {}, SortedQueue.new
|
96
92
|
end
|
97
93
|
|
94
|
+
def defuse_thread! thread
|
95
|
+
@requests.each { |r| r.defuse! if r.thread == thread }
|
96
|
+
end
|
97
|
+
private :defuse_thread!
|
98
|
+
|
98
99
|
def onNewNearestRequest &b
|
99
100
|
@onNewNearestRequest = b
|
100
101
|
end
|
@@ -102,13 +103,19 @@ module FrugalTimeout
|
|
102
103
|
# Purge and enforce expired timeouts. Only enforce once for each thread,
|
103
104
|
# even if multiple timeouts for that thread expire at once.
|
104
105
|
def purgeExpired
|
105
|
-
filter, now = {}, MonotonicTime.now
|
106
|
-
@requests.reject_and_get! { |r| r.at <= now }.each { |r|
|
107
|
-
r.enforceTimeout filter
|
108
|
-
}
|
109
|
-
|
106
|
+
expiredRequests, filter, now = nil, {}, MonotonicTime.now
|
110
107
|
@requests.synchronize {
|
111
|
-
@
|
108
|
+
@requests.reject_and_get! { |r| r.at <= now }.each { |r|
|
109
|
+
next if filter[r.thread]
|
110
|
+
|
111
|
+
r.enforceTimeout
|
112
|
+
defuse_thread! r.thread
|
113
|
+
filter[r.thread] = true
|
114
|
+
}
|
115
|
+
|
116
|
+
# It's necessary to call onNewNearestRequest inside synchronize as other
|
117
|
+
# threads may #queue requests.
|
118
|
+
@onNewNearestRequest.call @requests.first unless @requests.empty?
|
112
119
|
}
|
113
120
|
end
|
114
121
|
|
@@ -123,56 +130,73 @@ module FrugalTimeout
|
|
123
130
|
end
|
124
131
|
|
125
132
|
# {{{1 SleeperNotifier
|
133
|
+
# Executes callback when a request expires.
|
134
|
+
# 1. Set callback to execute with #onExpiry=.
|
135
|
+
# 2. Set expiry time with #expireAt.
|
136
|
+
# 3. After the expiry time comes, execute the callback.
|
137
|
+
#
|
138
|
+
# It's possible to set a new expiry time before the time set previously
|
139
|
+
# expires. In this case, processing of the old request stops and the new
|
140
|
+
# request processing starts.
|
126
141
|
class SleeperNotifier # :nodoc:
|
127
142
|
include MonitorMixin
|
128
143
|
|
144
|
+
DO_NOTHING = proc {}
|
145
|
+
|
129
146
|
def initialize
|
130
147
|
super()
|
131
|
-
@condVar, @
|
148
|
+
@condVar, @expireAt, @onExpiry = new_cond, nil, DO_NOTHING
|
132
149
|
|
133
150
|
@thread = Thread.new {
|
134
151
|
loop {
|
135
|
-
@onExpiry.call if synchronize {
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
@request = nil
|
141
|
-
true
|
152
|
+
synchronize { @onExpiry }.call if synchronize {
|
153
|
+
# Sleep forever until a request comes in.
|
154
|
+
unless @expireAt
|
155
|
+
wait
|
156
|
+
next
|
142
157
|
end
|
158
|
+
|
159
|
+
timeLeft = calcTimeLeft
|
160
|
+
disposeOfRequest
|
161
|
+
elapsedTime = MonotonicTime.measure { wait timeLeft }
|
162
|
+
|
163
|
+
elapsedTime >= timeLeft
|
143
164
|
}
|
144
165
|
}
|
145
166
|
}
|
146
167
|
ObjectSpace.define_finalizer self, proc { @thread.kill }
|
147
168
|
end
|
148
169
|
|
149
|
-
def
|
170
|
+
def onExpiry &b
|
171
|
+
synchronize { @onExpiry = b || DO_NOTHING }
|
172
|
+
end
|
173
|
+
|
174
|
+
def expireAt time
|
150
175
|
synchronize {
|
151
|
-
|
176
|
+
@expireAt = time
|
177
|
+
signalThread
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
152
182
|
|
153
|
-
|
183
|
+
def calcTimeLeft
|
184
|
+
synchronize {
|
185
|
+
delay = @expireAt - MonotonicTime.now
|
154
186
|
delay < 0 ? 0 : delay
|
155
187
|
}
|
156
188
|
end
|
157
|
-
private :latestDelay
|
158
189
|
|
159
|
-
def
|
160
|
-
@
|
190
|
+
def disposeOfRequest
|
191
|
+
@expireAt = nil
|
161
192
|
end
|
162
|
-
private :notify
|
163
193
|
|
164
|
-
def
|
165
|
-
@
|
194
|
+
def signalThread
|
195
|
+
@condVar.signal
|
166
196
|
end
|
167
197
|
|
168
|
-
|
169
|
-
|
170
|
-
# 3. The latest passed request expires and @onExpiry is called. Goto 1.
|
171
|
-
def sleepUntilExpires request
|
172
|
-
synchronize {
|
173
|
-
@request = request
|
174
|
-
notify
|
175
|
-
}
|
198
|
+
def wait sec=nil
|
199
|
+
@condVar.wait sec
|
176
200
|
end
|
177
201
|
end
|
178
202
|
|
@@ -185,6 +209,10 @@ module FrugalTimeout
|
|
185
209
|
@array, @unsorted = storage, false
|
186
210
|
end
|
187
211
|
|
212
|
+
def each &b
|
213
|
+
synchronize { @array.each &b }
|
214
|
+
end
|
215
|
+
|
188
216
|
def empty?
|
189
217
|
synchronize { @array.empty? }
|
190
218
|
end
|
@@ -249,7 +277,7 @@ module FrugalTimeout
|
|
249
277
|
@requestQueue = RequestQueue.new
|
250
278
|
sleeper = SleeperNotifier.new
|
251
279
|
@requestQueue.onNewNearestRequest { |request|
|
252
|
-
sleeper.
|
280
|
+
sleeper.expireAt request.at
|
253
281
|
}
|
254
282
|
sleeper.onExpiry { @requestQueue.purgeExpired }
|
255
283
|
|
@@ -263,18 +291,22 @@ module FrugalTimeout
|
|
263
291
|
end'
|
264
292
|
end
|
265
293
|
|
294
|
+
def self.on_ensure &b # :nodoc:
|
295
|
+
@onEnsure = b
|
296
|
+
end
|
297
|
+
|
266
298
|
# Same as Timeout.timeout()
|
267
299
|
def self.timeout sec, klass=Error
|
268
300
|
return yield sec if sec.nil? || sec <= 0
|
269
301
|
|
270
|
-
|
302
|
+
innerException = Class.new Timeout::ExitException
|
303
|
+
request = @requestQueue.queue(sec, innerException)
|
271
304
|
begin
|
272
305
|
yield sec
|
273
|
-
rescue
|
274
|
-
raise
|
275
|
-
|
276
|
-
raise request.klass, e.message, e.backtrace
|
306
|
+
rescue innerException => e
|
307
|
+
raise klass, e.message, e.backtrace
|
277
308
|
ensure
|
309
|
+
@onEnsure.call if @onEnsure
|
278
310
|
request.defuse!
|
279
311
|
end
|
280
312
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: frugal_timeout
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2014-01-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -43,22 +43,6 @@ dependencies:
|
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '1.2'
|
46
|
-
- !ruby/object:Gem::Dependency
|
47
|
-
name: null_object
|
48
|
-
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
|
-
requirements:
|
51
|
-
- - ~>
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: '0.0'
|
54
|
-
type: :runtime
|
55
|
-
prerelease: false
|
56
|
-
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
|
-
requirements:
|
59
|
-
- - ~>
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0.0'
|
62
46
|
description: Timeout.timeout replacement that uses only 1 thread
|
63
47
|
email: ledestin@gmail.com
|
64
48
|
executables: []
|