threadpool 0.1.0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/threadpool.rb +159 -10
  2. data/threadpool.gemspec +1 -1
  3. metadata +3 -3
@@ -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 @shutdown
134
+ raise 'unable to add work while shutting down' if shutdown?
64
135
 
65
- @todo << [args, block]
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
- work = nil
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 @shutdown
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 @shutdown
240
+ if shutdown?
128
241
  continue = false
129
242
 
130
243
  break
131
244
  end
132
245
  end
133
246
 
134
- work = @todo.shift if continue
247
+ task = @todo.shift if continue
135
248
  }
136
249
 
137
- break unless continue
250
+ break if !continue || shutdown?
138
251
 
139
- (work.last || @block).call(*work.first)
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new {|s|
2
2
  s.name = 'threadpool'
3
- s.version = '0.1.0.1'
3
+ s.version = '0.1.1'
4
4
  s.author = 'meh.'
5
5
  s.email = 'meh@paranoici.org'
6
6
  s.homepage = 'http://github.com/meh/ruby-threadpool'
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.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-03-29 00:00:00.000000000 Z
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.11
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.