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.
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.