threadpool 0.1.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/threadpool.rb +159 -10
- data/threadpool.gemspec +1 -1
- metadata +3 -3
data/lib/threadpool.rb
CHANGED
@@ -11,6 +11,72 @@
|
|
11
11
|
require 'thread'
|
12
12
|
|
13
13
|
class ThreadPool
|
14
|
+
class Task
|
15
|
+
Timeout = Class.new(Exception)
|
16
|
+
Asked = Class.new(Exception)
|
17
|
+
|
18
|
+
attr_reader :pool, :timeout, :exception, :thread, :started_at
|
19
|
+
|
20
|
+
def initialize (pool, *args, &block)
|
21
|
+
@pool = pool
|
22
|
+
@arguments = args
|
23
|
+
@block = block
|
24
|
+
end
|
25
|
+
|
26
|
+
def running?; @running; end
|
27
|
+
def finished?; @finished; end
|
28
|
+
def timeout?; @timedout; end
|
29
|
+
def terminated?; @terminated; end
|
30
|
+
|
31
|
+
def execute (thread)
|
32
|
+
return if terminated? || running? || finished?
|
33
|
+
|
34
|
+
@thread = thread
|
35
|
+
@running = true
|
36
|
+
@started_at = Time.now
|
37
|
+
|
38
|
+
pool.wake_up_timeout
|
39
|
+
|
40
|
+
begin
|
41
|
+
@block.call(*@arguments)
|
42
|
+
rescue Exception => e
|
43
|
+
if e.is_a? Timeout
|
44
|
+
@timedout = true
|
45
|
+
elsif e.is_a? Asked
|
46
|
+
return
|
47
|
+
else
|
48
|
+
@exception = reason
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
@running = false
|
53
|
+
@finished = true
|
54
|
+
@thread = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def terminate! (exception = Asked)
|
58
|
+
return if terminated? || finished? || timeout?
|
59
|
+
|
60
|
+
@terminated = true
|
61
|
+
|
62
|
+
return unless running?
|
63
|
+
|
64
|
+
@thread.raise exception
|
65
|
+
end
|
66
|
+
|
67
|
+
def timeout!
|
68
|
+
terminate! Timeout
|
69
|
+
end
|
70
|
+
|
71
|
+
def timeout_after (time)
|
72
|
+
@timeout = time
|
73
|
+
|
74
|
+
pool.timeout_for self, time
|
75
|
+
|
76
|
+
self
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
14
80
|
attr_reader :min, :max, :spawned
|
15
81
|
|
16
82
|
def initialize (min, max = nil, &block)
|
@@ -21,8 +87,9 @@ class ThreadPool
|
|
21
87
|
@cond = ConditionVariable.new
|
22
88
|
@mutex = Mutex.new
|
23
89
|
|
24
|
-
@todo
|
25
|
-
@workers
|
90
|
+
@todo = []
|
91
|
+
@workers = []
|
92
|
+
@timeouts = {}
|
26
93
|
|
27
94
|
@spawned = 0
|
28
95
|
@waiting = 0
|
@@ -37,6 +104,8 @@ class ThreadPool
|
|
37
104
|
}
|
38
105
|
end
|
39
106
|
|
107
|
+
def shutdown?; @shutdown; end
|
108
|
+
|
40
109
|
def auto_trim?; @auto_trim; end
|
41
110
|
def auto_trim!; @auto_trim = true; end
|
42
111
|
def no_auto_trim!; @auto_trim = false; end
|
@@ -59,10 +128,12 @@ class ThreadPool
|
|
59
128
|
raise ArgumentError, 'you must pass a block'
|
60
129
|
end
|
61
130
|
|
131
|
+
task = Task.new(self, *args, &(block || @block))
|
132
|
+
|
62
133
|
@mutex.synchronize {
|
63
|
-
raise 'unable to add work while shutting down' if
|
134
|
+
raise 'unable to add work while shutting down' if shutdown?
|
64
135
|
|
65
|
-
@todo <<
|
136
|
+
@todo << task
|
66
137
|
|
67
138
|
if @waiting == 0 && @spawned < @max
|
68
139
|
spawn_thread
|
@@ -70,6 +141,8 @@ class ThreadPool
|
|
70
141
|
|
71
142
|
@cond.signal
|
72
143
|
}
|
144
|
+
|
145
|
+
task
|
73
146
|
end
|
74
147
|
|
75
148
|
alias << process
|
@@ -81,6 +154,8 @@ class ThreadPool
|
|
81
154
|
@cond.signal
|
82
155
|
end
|
83
156
|
}
|
157
|
+
|
158
|
+
self
|
84
159
|
end
|
85
160
|
|
86
161
|
def trim!
|
@@ -93,7 +168,45 @@ class ThreadPool
|
|
93
168
|
@cond.broadcast
|
94
169
|
}
|
95
170
|
|
171
|
+
wake_up_timeout
|
172
|
+
|
173
|
+
join
|
174
|
+
end
|
175
|
+
|
176
|
+
def join
|
177
|
+
@timeout.join if @timeout
|
178
|
+
|
96
179
|
@workers.first.join until @workers.empty?
|
180
|
+
|
181
|
+
self
|
182
|
+
end
|
183
|
+
|
184
|
+
def timeout_for (task, timeout)
|
185
|
+
unless @timeout
|
186
|
+
spawn_timeout_thread
|
187
|
+
end
|
188
|
+
|
189
|
+
@mutex.synchronize {
|
190
|
+
@timeouts[task] = timeout
|
191
|
+
|
192
|
+
wake_up_timeout
|
193
|
+
}
|
194
|
+
end
|
195
|
+
|
196
|
+
def shutdown_after (timeout)
|
197
|
+
Thread.new {
|
198
|
+
sleep timeout
|
199
|
+
|
200
|
+
shutdown
|
201
|
+
}
|
202
|
+
|
203
|
+
self
|
204
|
+
end
|
205
|
+
|
206
|
+
def wake_up_timeout
|
207
|
+
if @pipes
|
208
|
+
@pipes.last.write_nonblock 'x' rescue nil
|
209
|
+
end
|
97
210
|
end
|
98
211
|
|
99
212
|
private
|
@@ -102,7 +215,7 @@ private
|
|
102
215
|
|
103
216
|
thread = Thread.new {
|
104
217
|
loop do
|
105
|
-
|
218
|
+
task = nil
|
106
219
|
continue = true
|
107
220
|
|
108
221
|
@mutex.synchronize {
|
@@ -114,7 +227,7 @@ private
|
|
114
227
|
break
|
115
228
|
end
|
116
229
|
|
117
|
-
if
|
230
|
+
if shutdown?
|
118
231
|
continue = false
|
119
232
|
|
120
233
|
break
|
@@ -124,19 +237,19 @@ private
|
|
124
237
|
@cond.wait @mutex
|
125
238
|
@waiting -= 1
|
126
239
|
|
127
|
-
if
|
240
|
+
if shutdown?
|
128
241
|
continue = false
|
129
242
|
|
130
243
|
break
|
131
244
|
end
|
132
245
|
end
|
133
246
|
|
134
|
-
|
247
|
+
task = @todo.shift if continue
|
135
248
|
}
|
136
249
|
|
137
|
-
break
|
250
|
+
break if !continue || shutdown?
|
138
251
|
|
139
|
-
|
252
|
+
task.execute(thread)
|
140
253
|
|
141
254
|
trim if auto_trim? && @spawned > @min
|
142
255
|
end
|
@@ -151,4 +264,40 @@ private
|
|
151
264
|
|
152
265
|
thread
|
153
266
|
end
|
267
|
+
|
268
|
+
def spawn_timeout_thread
|
269
|
+
@pipes = IO.pipe
|
270
|
+
|
271
|
+
@timeout = Thread.new {
|
272
|
+
loop do
|
273
|
+
now = Time.now
|
274
|
+
timeout = @timeouts.map {|task, timeout|
|
275
|
+
next unless task.started_at
|
276
|
+
|
277
|
+
now - task.started_at + task.timeout
|
278
|
+
}.compact.min unless @timeouts.empty?
|
279
|
+
|
280
|
+
readable, = IO.select([@pipes.first], nil, nil, timeout)
|
281
|
+
|
282
|
+
break if shutdown?
|
283
|
+
|
284
|
+
if readable && !readable.empty?
|
285
|
+
readable.first.read_nonblock 1024
|
286
|
+
end
|
287
|
+
|
288
|
+
now = Time.now
|
289
|
+
@timeouts.each {|task, time|
|
290
|
+
next if !task.started_at || task.terminated? || task.finished?
|
291
|
+
|
292
|
+
if now > task.started_at + task.timeout
|
293
|
+
task.timeout!
|
294
|
+
end
|
295
|
+
}
|
296
|
+
|
297
|
+
@timeouts.reject! { |task, _| task.terminated? || task.finished? }
|
298
|
+
|
299
|
+
break if shutdown?
|
300
|
+
end
|
301
|
+
}
|
302
|
+
end
|
154
303
|
end
|
data/threadpool.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: threadpool
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
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: 2012-
|
12
|
+
date: 2012-06-01 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description:
|
15
15
|
email: meh@paranoici.org
|
@@ -40,7 +40,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
40
40
|
version: '0'
|
41
41
|
requirements: []
|
42
42
|
rubyforge_project:
|
43
|
-
rubygems_version: 1.8.
|
43
|
+
rubygems_version: 1.8.24
|
44
44
|
signing_key:
|
45
45
|
specification_version: 3
|
46
46
|
summary: A simple no-wasted-resources thread pool implementation.
|