seekingalpha_thread 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,488 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'thread'
12
+
13
+ # A pool is a container of a limited amount of threads to which you can add
14
+ # tasks to run.
15
+ #
16
+ # This is usually more performant and less memory intensive than creating a
17
+ # new thread for every task.
18
+ class Thread::Pool
19
+ # A task incapsulates a block being ran by the pool and the arguments to pass
20
+ # to it.
21
+ class Task
22
+ Timeout = Class.new(Exception)
23
+ Asked = Class.new(Exception)
24
+
25
+ attr_reader :pool, :timeout, :exception, :thread, :started_at, :result
26
+
27
+ # Create a task in the given pool which will pass the arguments to the
28
+ # block.
29
+ def initialize(pool, *args, &block)
30
+ @pool = pool
31
+ @arguments = args
32
+ @block = block
33
+
34
+ @running = false
35
+ @finished = false
36
+ @timedout = false
37
+ @terminated = false
38
+ end
39
+
40
+ def running?
41
+ @running
42
+ end
43
+
44
+ def finished?
45
+ @finished
46
+ end
47
+
48
+ def timeout?
49
+ @timedout
50
+ end
51
+
52
+ def terminated?
53
+ @terminated
54
+ end
55
+
56
+ # Execute the task.
57
+ def execute
58
+ return if terminated? || running? || finished?
59
+
60
+ @thread = Thread.current
61
+ @running = true
62
+ @started_at = Time.now
63
+
64
+ pool.__send__ :wake_up_timeout
65
+
66
+ begin
67
+ @result = @block.call(*@arguments)
68
+ rescue Exception => reason
69
+ if reason.is_a? Timeout
70
+ @timedout = true
71
+ elsif reason.is_a? Asked
72
+ return
73
+ else
74
+ @exception = reason
75
+ raise @exception if Thread::Pool.abort_on_exception
76
+ end
77
+ end
78
+
79
+ @running = false
80
+ @finished = true
81
+ @thread = nil
82
+ end
83
+
84
+ # Raise an exception in the thread used by the task.
85
+ def raise(exception)
86
+ @thread.raise(exception)
87
+ end
88
+
89
+ # Terminate the exception with an optionally given exception.
90
+ def terminate!(exception = Asked)
91
+ return if terminated? || finished? || timeout?
92
+
93
+ @terminated = true
94
+
95
+ return unless running?
96
+
97
+ self.raise exception
98
+ end
99
+
100
+ # Force the task to timeout.
101
+ def timeout!
102
+ terminate! Timeout
103
+ end
104
+
105
+ # Timeout the task after the given time.
106
+ def timeout_after(time)
107
+ @timeout = time
108
+
109
+ pool.__send__ :timeout_for, self, time
110
+
111
+ self
112
+ end
113
+ end
114
+
115
+ attr_reader :min, :max, :spawned, :waiting
116
+
117
+ # Create the pool with minimum and maximum threads.
118
+ #
119
+ # The pool will start with the minimum amount of threads created and will
120
+ # spawn new threads until the max is reached in case of need.
121
+ #
122
+ # A default block can be passed, which will be used to {#process} the passed
123
+ # data.
124
+ def initialize(min, max = nil, &block)
125
+ @min = min
126
+ @max = max || min
127
+ @block = block
128
+
129
+ @cond = ConditionVariable.new
130
+ @mutex = Mutex.new
131
+
132
+ @done = ConditionVariable.new
133
+ @done_mutex = Mutex.new
134
+
135
+ @todo = []
136
+ @workers = []
137
+ @timeouts = {}
138
+
139
+ @spawned = 0
140
+ @waiting = 0
141
+ @shutdown = false
142
+ @trim_requests = 0
143
+ @auto_trim = false
144
+ @idle_trim = nil
145
+
146
+ @mutex.synchronize {
147
+ min.times.with_index { |index|
148
+ begin
149
+ spawn_thread
150
+ rescue
151
+ puts "can't create more than #{index} threads"
152
+ break
153
+ end
154
+ }
155
+ }
156
+ end
157
+
158
+ # Check if the pool has been shut down.
159
+ def shutdown?
160
+ !!@shutdown
161
+ end
162
+
163
+ # Check if auto trimming is enabled.
164
+ def auto_trim?
165
+ @auto_trim
166
+ end
167
+
168
+ # Enable auto trimming, unneeded threads will be deleted until the minimum
169
+ # is reached.
170
+ def auto_trim!
171
+ @auto_trim = true
172
+
173
+ self
174
+ end
175
+
176
+ # Disable auto trimming.
177
+ def no_auto_trim!
178
+ @auto_trim = false
179
+
180
+ self
181
+ end
182
+
183
+ # Check if idle trimming is enabled.
184
+ def idle_trim?
185
+ !@idle_trim.nil?
186
+ end
187
+
188
+ # Enable idle trimming. Unneeded threads will be deleted after the given number of seconds of inactivity.
189
+ # The minimum number of threads is respeced.
190
+ def idle_trim!(timeout)
191
+ @idle_trim = timeout
192
+
193
+ self
194
+ end
195
+
196
+ # Turn of idle trimming.
197
+ def no_idle_trim!
198
+ @idle_trim = nil
199
+
200
+ self
201
+ end
202
+
203
+ # Returns the actual amount of workers
204
+ def size
205
+ @workers.size
206
+ end
207
+
208
+ # Resize the pool with the passed arguments.
209
+ def resize(min, max = nil)
210
+ @min = min
211
+ @max = max || min
212
+
213
+ trim!
214
+ end
215
+
216
+ # Get the amount of tasks that still have to be run.
217
+ def backlog
218
+ @mutex.synchronize {
219
+ @todo.length
220
+ }
221
+ end
222
+
223
+ # Are all tasks consumed?
224
+ def done?
225
+ @mutex.synchronize {
226
+ _done?
227
+ }
228
+ end
229
+
230
+ # Wait until all tasks are consumed. The caller will be blocked until then.
231
+ def wait(what = :idle)
232
+ case what
233
+ when :done
234
+ until done?
235
+ @done_mutex.synchronize {
236
+ break if _done?
237
+
238
+ @done.wait @done_mutex
239
+ }
240
+ end
241
+
242
+ when :idle
243
+ until idle?
244
+ @done_mutex.synchronize {
245
+ break if _idle?
246
+
247
+ @done.wait @done_mutex
248
+ }
249
+ end
250
+ end
251
+
252
+ self
253
+ end
254
+
255
+ # Check if there are idle workers.
256
+ def idle?
257
+ @mutex.synchronize {
258
+ _idle?
259
+ }
260
+ end
261
+
262
+ # Add a task to the pool which will execute the block with the given
263
+ # argument.
264
+ #
265
+ # If no block is passed the default block will be used if present, an
266
+ # ArgumentError will be raised otherwise.
267
+ def process(*args, &block)
268
+ unless block || @block
269
+ raise ArgumentError, 'you must pass a block'
270
+ end
271
+
272
+ task = Task.new(self, *args, &(block || @block))
273
+
274
+ @mutex.synchronize {
275
+ raise 'unable to add work while shutting down' if shutdown?
276
+
277
+ @todo << task
278
+
279
+ if @waiting == 0 && @spawned < @max
280
+ spawn_thread
281
+ end
282
+
283
+ @cond.signal
284
+ }
285
+
286
+ task
287
+ end
288
+
289
+ alias << process
290
+
291
+ # Trim the unused threads, if forced threads will be trimmed even if there
292
+ # are tasks waiting.
293
+ def trim(force = false)
294
+ @mutex.synchronize {
295
+ if (force || @waiting > 0) && @spawned - @trim_requests > @min
296
+ @trim_requests += 1
297
+ @cond.signal
298
+ end
299
+ }
300
+
301
+ self
302
+ end
303
+
304
+ # Force #{trim}.
305
+ def trim!
306
+ trim true
307
+ end
308
+
309
+ # Shut down the pool instantly without finishing to execute tasks.
310
+ def shutdown!
311
+ @mutex.synchronize {
312
+ @shutdown = :now
313
+ @cond.broadcast
314
+ }
315
+
316
+ wake_up_timeout
317
+
318
+ self
319
+ end
320
+
321
+ # Shut down the pool, it will block until all tasks have finished running.
322
+ def shutdown
323
+ @mutex.synchronize {
324
+ @shutdown = :nicely
325
+ @cond.broadcast
326
+ }
327
+
328
+ until @workers.empty?
329
+ if worker = @workers.first
330
+ worker.join
331
+ end
332
+ end
333
+
334
+ if @timeout
335
+ @shutdown = :now
336
+
337
+ wake_up_timeout
338
+
339
+ @timeout.join
340
+ end
341
+ end
342
+
343
+ # Shutdown the pool after a given amount of time.
344
+ def shutdown_after(timeout)
345
+ Thread.new {
346
+ sleep timeout
347
+
348
+ shutdown
349
+ }
350
+ end
351
+
352
+ class << self
353
+ # If true, tasks will allow raised exceptions to pass through.
354
+ #
355
+ # Similar to Thread.abort_on_exception
356
+ attr_accessor :abort_on_exception
357
+ end
358
+
359
+ private
360
+ def timeout_for(task, timeout)
361
+ unless @timeout
362
+ spawn_timeout_thread
363
+ end
364
+
365
+ @mutex.synchronize {
366
+ @timeouts[task] = timeout
367
+
368
+ wake_up_timeout
369
+ }
370
+ end
371
+
372
+ def wake_up_timeout
373
+ if defined? @pipes
374
+ @pipes.last.write_nonblock 'x' rescue nil
375
+ end
376
+ end
377
+
378
+ def spawn_thread
379
+ @spawned += 1
380
+
381
+ thread = Thread.new {
382
+ loop do
383
+ task = @mutex.synchronize {
384
+ if @todo.empty?
385
+ while @todo.empty?
386
+ if @trim_requests > 0
387
+ @trim_requests -= 1
388
+
389
+ break
390
+ end
391
+
392
+ break if shutdown?
393
+
394
+ @waiting += 1
395
+
396
+ done!
397
+
398
+ if @idle_trim and @spawned > @min
399
+ check_time = Time.now + @idle_trim
400
+ @cond.wait @mutex, @idle_trim
401
+ @trim_requests += 1 if Time.now >= check_time && @spawned - @trim_requests > @min
402
+ else
403
+ @cond.wait @mutex
404
+ end
405
+
406
+ @waiting -= 1
407
+ end
408
+
409
+ break if @todo.empty? && shutdown?
410
+ end
411
+
412
+ @todo.shift
413
+ } or break
414
+
415
+ task.execute
416
+
417
+ break if @shutdown == :now
418
+
419
+ trim if auto_trim? && @spawned > @min
420
+ end
421
+
422
+ @mutex.synchronize {
423
+ @spawned -= 1
424
+ @workers.delete thread
425
+ }
426
+ }
427
+
428
+ @workers << thread
429
+
430
+ thread
431
+ end
432
+
433
+ def spawn_timeout_thread
434
+ @pipes = IO.pipe
435
+ @timeout = Thread.new {
436
+ loop do
437
+ now = Time.now
438
+ timeout = @timeouts.map {|task, time|
439
+ next unless task.started_at
440
+
441
+ now - task.started_at + task.timeout
442
+ }.compact.min unless @timeouts.empty?
443
+
444
+ readable, = IO.select([@pipes.first], nil, nil, timeout)
445
+
446
+ break if @shutdown == :now
447
+
448
+ if readable && !readable.empty?
449
+ readable.first.read_nonblock 1024
450
+ end
451
+
452
+ now = Time.now
453
+ @timeouts.each {|task, time|
454
+ next if !task.started_at || task.terminated? || task.finished?
455
+
456
+ if now > task.started_at + task.timeout
457
+ task.timeout!
458
+ end
459
+ }
460
+
461
+ @timeouts.reject! { |task, _| task.terminated? || task.finished? }
462
+
463
+ break if @shutdown == :now
464
+ end
465
+ }
466
+ end
467
+
468
+ def _done?
469
+ @todo.empty? and @waiting == @spawned
470
+ end
471
+
472
+ def _idle?
473
+ @todo.length < @waiting
474
+ end
475
+
476
+ def done!
477
+ @done_mutex.synchronize {
478
+ @done.broadcast if _done? or _idle?
479
+ }
480
+ end
481
+ end
482
+
483
+ class Thread
484
+ # Helper to create a pool.
485
+ def self.pool(*args, &block)
486
+ Thread::Pool.new(*args, &block)
487
+ end
488
+ end