utilrb 2.0.0 → 2.0.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/Manifest.txt +12 -4
- data/ext/utilrb/extconf.rb +5 -5
- data/ext/utilrb/proc.c +3 -3
- data/ext/utilrb/utilrb.cc +0 -2
- data/ext/utilrb/weakref.cc +3 -3
- data/lib/utilrb/common.rb +1 -1
- data/lib/utilrb/event_loop.rb +969 -0
- data/lib/utilrb/logger/silent.rb +10 -0
- data/lib/utilrb/models/inherited_enumerable.rb +341 -0
- data/lib/utilrb/models/registration.rb +115 -0
- data/lib/utilrb/pathname/find_matching_parent.rb +20 -0
- data/lib/utilrb/pathname.rb +3 -0
- data/lib/utilrb/qt/mime_data/mime_data.rb +18 -0
- data/lib/utilrb/qt/variant/from_ruby.rb +30 -0
- data/lib/utilrb/thread_pool.rb +592 -0
- data/test/test_array.rb +1 -1
- data/test/test_dir.rb +1 -1
- data/test/test_enumerable.rb +1 -1
- data/test/test_event_loop.rb +1 -1
- data/test/test_exception.rb +1 -1
- data/test/test_gc.rb +1 -1
- data/test/test_hash.rb +1 -1
- data/test/test_kernel.rb +3 -13
- data/test/test_misc.rb +1 -1
- data/test/test_models.rb +1 -1
- data/test/test_module.rb +1 -1
- data/test/test_object.rb +1 -1
- data/test/test_objectstats.rb +1 -1
- data/test/test_proc.rb +1 -1
- data/test/test_set.rb +1 -1
- data/test/test_thread_pool.rb +1 -1
- data/test/test_time.rb +1 -1
- data/test/test_unbound_method.rb +1 -1
- metadata +30 -25
- data/ext/utilrb/ruby_internals-1.8.h +0 -72
- data/ext/utilrb/ruby_internals-1.9.h +0 -71
- data/ext/utilrb/swap.cc +0 -31
- data/lib/utilrb/kernel/swap.rb +0 -2
@@ -0,0 +1,592 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'set'
|
3
|
+
require 'utilrb/kernel/options'
|
4
|
+
|
5
|
+
module Utilrb
|
6
|
+
# ThreadPool implementation inspired by
|
7
|
+
# https://github.com/meh/ruby-threadpool
|
8
|
+
#
|
9
|
+
# @example Using a thread pool of 10 threads
|
10
|
+
# pool = ThreadPool.new(10)
|
11
|
+
# 0.upto(9) do
|
12
|
+
# pool.process do
|
13
|
+
# sleep 1
|
14
|
+
# puts "done"
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
# pool.shutdown
|
18
|
+
# pool.join
|
19
|
+
#
|
20
|
+
# @author Alexander Duda <Alexander.Duda@dfki.de>
|
21
|
+
class ThreadPool
|
22
|
+
# A Task is executed by the thread pool as soon as
|
23
|
+
# a free thread is available.
|
24
|
+
#
|
25
|
+
# @author Alexander Duda <Alexander.Duda@dfki.de>
|
26
|
+
class Task
|
27
|
+
Asked = Class.new(Exception)
|
28
|
+
|
29
|
+
# The sync key is used to speifiy that a given task must not run in
|
30
|
+
# paralles with another task having the same sync key. If no key is
|
31
|
+
# set there are no such constrains for the taks.
|
32
|
+
#
|
33
|
+
# @return the sync key
|
34
|
+
attr_reader :sync_key
|
35
|
+
|
36
|
+
# Thread pool the task belongs to
|
37
|
+
#
|
38
|
+
# @return [ThreadPool] the thread pool
|
39
|
+
attr_reader :pool
|
40
|
+
|
41
|
+
# State of the task
|
42
|
+
#
|
43
|
+
# @return [:waiting,:running,:stopping,:finished,:terminated,:exception] the state
|
44
|
+
attr_reader :state
|
45
|
+
|
46
|
+
# The exception thrown by the custom code block
|
47
|
+
#
|
48
|
+
# @return [Exception] the exception
|
49
|
+
attr_reader :exception
|
50
|
+
|
51
|
+
# The thread the task was assigned to
|
52
|
+
#
|
53
|
+
# return [Thread] the thread
|
54
|
+
attr_reader :thread
|
55
|
+
|
56
|
+
# The time the task was queued
|
57
|
+
#
|
58
|
+
# return [Time] the time
|
59
|
+
attr_accessor :queued_at
|
60
|
+
|
61
|
+
# The time the task was started
|
62
|
+
#
|
63
|
+
# return [Time] the time
|
64
|
+
attr_reader :started_at
|
65
|
+
|
66
|
+
# The time the task was stopped or finished
|
67
|
+
#
|
68
|
+
# return [Time] the time
|
69
|
+
attr_reader :stopped_at
|
70
|
+
|
71
|
+
# Result of the code block call
|
72
|
+
attr_reader :result
|
73
|
+
|
74
|
+
# Custom description which can be used
|
75
|
+
# to store a human readable object
|
76
|
+
attr_accessor :description
|
77
|
+
|
78
|
+
# Checks if the task was started
|
79
|
+
#
|
80
|
+
# @return [Boolean]
|
81
|
+
def started?; @state != :waiting; end
|
82
|
+
|
83
|
+
# Checks if the task is running
|
84
|
+
#
|
85
|
+
# @return [Boolean]
|
86
|
+
def running?; @state == :running; end
|
87
|
+
|
88
|
+
# Checks if the task is going to be stopped
|
89
|
+
#
|
90
|
+
# @return [Boolean]
|
91
|
+
def stopping?; @state == :stopping; end
|
92
|
+
|
93
|
+
# Checks if the task was stopped or finished.
|
94
|
+
# This also includes cases where
|
95
|
+
# an exception was raised by the custom code block.
|
96
|
+
#
|
97
|
+
# @return [Boolean]
|
98
|
+
def finished?; started? && !running? && !stopping?; end
|
99
|
+
|
100
|
+
# Checks if the task was successfully finished.
|
101
|
+
# This means no exceptions, termination or timed out occurred
|
102
|
+
#
|
103
|
+
# @return [Boolean]
|
104
|
+
def successfull?; @state == :finished; end
|
105
|
+
|
106
|
+
# Checks if the task was terminated.
|
107
|
+
#
|
108
|
+
# @return [Boolean]
|
109
|
+
def terminated?; @state == :terminated; end
|
110
|
+
|
111
|
+
# Checks if an exception occurred.
|
112
|
+
#
|
113
|
+
# @return [Boolean]
|
114
|
+
def exception?; @state == :exception; end
|
115
|
+
|
116
|
+
# A new task which can be added to the work queue of a {ThreadPool}.
|
117
|
+
# If a sync key is given no task having the same key will be
|
118
|
+
# executed in parallel which is useful for instance member calls
|
119
|
+
# which are not thread safe.
|
120
|
+
#
|
121
|
+
# @param [Hash] options The options of the task.
|
122
|
+
# @option options [Object] :sync_key The sync key
|
123
|
+
# @option options [Proc] :callback The callback
|
124
|
+
# @option options [Object] :default Default value returned when an error ocurred which was handled.
|
125
|
+
# @param [Array] args The arguments for the code block
|
126
|
+
# @param [#call] block The code block
|
127
|
+
def initialize (options = Hash.new,*args, &block)
|
128
|
+
unless block
|
129
|
+
raise ArgumentError, 'you must pass a work block to initialize a new Task.'
|
130
|
+
end
|
131
|
+
options = Kernel.validate_options(options,{:sync_key => nil,:default => nil,:callback => nil})
|
132
|
+
@sync_key = options[:sync_key]
|
133
|
+
@arguments = args
|
134
|
+
@default = options[:default]
|
135
|
+
@callback = options[:callback]
|
136
|
+
@block = block
|
137
|
+
@mutex = Mutex.new
|
138
|
+
@pool = nil
|
139
|
+
@state_temp = nil
|
140
|
+
@state = nil
|
141
|
+
reset
|
142
|
+
end
|
143
|
+
|
144
|
+
# Resets the tasks.
|
145
|
+
# This can be used to requeue a task that is already finished
|
146
|
+
def reset
|
147
|
+
if finished? || !started?
|
148
|
+
@mutex.synchronize do
|
149
|
+
@result = @default
|
150
|
+
@state = :waiting
|
151
|
+
@exception = nil
|
152
|
+
@started_at = nil
|
153
|
+
@queued_at = nil
|
154
|
+
@stopped_at = nil
|
155
|
+
end
|
156
|
+
else
|
157
|
+
raise RuntimeError,"cannot reset a task which is not finished"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# returns true if the task has a default return vale
|
162
|
+
# @return [Boolean]
|
163
|
+
def default?
|
164
|
+
@mutex.synchronize do
|
165
|
+
@default != nil
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
#sets all internal state to running
|
170
|
+
#call execute after that.
|
171
|
+
def pre_execute(pool=nil)
|
172
|
+
@mutex.synchronize do
|
173
|
+
#store current thread to be able to terminate
|
174
|
+
#the thread
|
175
|
+
@pool = pool
|
176
|
+
@thread = Thread.current
|
177
|
+
@started_at = Time.now
|
178
|
+
@state = :running
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Executes the task.
|
183
|
+
# Should be called from a worker thread after pre_execute was called.
|
184
|
+
# After execute returned and the task was deleted
|
185
|
+
# from any internal list finalize must be called
|
186
|
+
# to propagate the task state.
|
187
|
+
def execute()
|
188
|
+
raise RuntimeError, "call pre_execute ThreadPool::Task first. Current state is #{@state} but :running was expected" if @state != :running
|
189
|
+
@state_temp = begin
|
190
|
+
@result = @block.call(*@arguments)
|
191
|
+
:finished
|
192
|
+
rescue Exception => e
|
193
|
+
@exception = e
|
194
|
+
if e.is_a? Asked
|
195
|
+
:terminated
|
196
|
+
else
|
197
|
+
:exception
|
198
|
+
end
|
199
|
+
end
|
200
|
+
@stopped_at = Time.now
|
201
|
+
end
|
202
|
+
|
203
|
+
# propagates the tasks state
|
204
|
+
# should be called after execute
|
205
|
+
def finalize
|
206
|
+
@mutex.synchronize do
|
207
|
+
@thread = nil
|
208
|
+
@state = @state_temp
|
209
|
+
@pool = nil
|
210
|
+
end
|
211
|
+
begin
|
212
|
+
@callback.call @result,@exception if @callback
|
213
|
+
rescue Exception => e
|
214
|
+
ThreadPool.report_exception("thread_pool: in #{self}, callback #{@callback} failed", e)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Terminates the task if it is running
|
219
|
+
def terminate!(exception = Asked)
|
220
|
+
@mutex.synchronize do
|
221
|
+
return unless running?
|
222
|
+
@state = :stopping
|
223
|
+
@thread.raise exception
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Called from the worker thread when the work is done
|
228
|
+
#
|
229
|
+
# @yield [Object,Exception] The callback
|
230
|
+
def callback(&block)
|
231
|
+
@mutex.synchronize do
|
232
|
+
@callback = block
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Returns the number of seconds the task is or was running
|
237
|
+
# at the given point in time
|
238
|
+
#
|
239
|
+
# @param [Time] time The point in time.
|
240
|
+
# @return[Float]
|
241
|
+
def time_elapsed(time = Time.now)
|
242
|
+
#no need to synchronize here
|
243
|
+
if running?
|
244
|
+
(time-@started_at).to_f
|
245
|
+
elsif finished?
|
246
|
+
(@stopped_at-@started_at).to_f
|
247
|
+
else
|
248
|
+
0
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# The minimum number of worker threads.
|
254
|
+
#
|
255
|
+
# @return [Fixnum]
|
256
|
+
attr_reader :min
|
257
|
+
|
258
|
+
# The maximum number of worker threads.
|
259
|
+
#
|
260
|
+
# @return [Fixnum]
|
261
|
+
attr_reader :max
|
262
|
+
|
263
|
+
# The real number of worker threads.
|
264
|
+
#
|
265
|
+
# @return [Fixnum]
|
266
|
+
attr_reader :spawned
|
267
|
+
|
268
|
+
# The number of worker threads waiting for work.
|
269
|
+
#
|
270
|
+
# @return [Fixnum]
|
271
|
+
attr_reader :waiting
|
272
|
+
|
273
|
+
# The average execution time of a (running) task.
|
274
|
+
#
|
275
|
+
# @return [Float]
|
276
|
+
attr_reader :avg_run_time
|
277
|
+
|
278
|
+
# The average waiting time of a task before being executed.
|
279
|
+
#
|
280
|
+
# @return [Float]
|
281
|
+
attr_reader :avg_wait_time
|
282
|
+
|
283
|
+
# Auto trim automatically reduces the number of worker threads if there are too many
|
284
|
+
# threads waiting for work.
|
285
|
+
# @return [Boolean]
|
286
|
+
attr_accessor :auto_trim
|
287
|
+
|
288
|
+
# A ThreadPool
|
289
|
+
#
|
290
|
+
# @param [Fixnum] min the minimum number of threads
|
291
|
+
# @param [Fixnum] max the maximum number of threads
|
292
|
+
def initialize (min = 5, max = min)
|
293
|
+
@min = min
|
294
|
+
@max = max
|
295
|
+
|
296
|
+
@cond = ConditionVariable.new
|
297
|
+
@cond_sync_key = ConditionVariable.new
|
298
|
+
@mutex = Mutex.new
|
299
|
+
|
300
|
+
@tasks_waiting = [] # tasks waiting for execution
|
301
|
+
@tasks_running = [] # tasks which are currently running
|
302
|
+
|
303
|
+
# Statistics
|
304
|
+
@avg_run_time = 0 # average run time of a task in s [Float]
|
305
|
+
@avg_wait_time = 0 # average time a task has to wait for execution in s [Float]
|
306
|
+
|
307
|
+
@workers = [] # thread pool
|
308
|
+
@spawned = 0
|
309
|
+
@waiting = 0
|
310
|
+
@shutdown = false
|
311
|
+
@callback_on_task_finished = nil
|
312
|
+
@pipes = nil
|
313
|
+
@sync_keys = Set.new
|
314
|
+
|
315
|
+
@trim_requests = 0
|
316
|
+
@auto_trim = false
|
317
|
+
|
318
|
+
@mutex.synchronize do
|
319
|
+
min.times do
|
320
|
+
spawn_thread
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# sets the minimum number of threads
|
326
|
+
def min=(val)
|
327
|
+
resize(val,max)
|
328
|
+
end
|
329
|
+
|
330
|
+
# sets the maximum number of threads
|
331
|
+
def max=(val)
|
332
|
+
resize(min,val)
|
333
|
+
end
|
334
|
+
|
335
|
+
# returns the current used sync_keys
|
336
|
+
def sync_keys
|
337
|
+
@mutex.synchronize do
|
338
|
+
@sync_keys.clone
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def clear
|
343
|
+
shutdown
|
344
|
+
join
|
345
|
+
rescue Exception
|
346
|
+
ensure
|
347
|
+
@shutdown = false
|
348
|
+
end
|
349
|
+
|
350
|
+
# Checks if the thread pool is shutting down all threads.
|
351
|
+
#
|
352
|
+
# @return [boolean]
|
353
|
+
def shutdown?; @shutdown; end
|
354
|
+
|
355
|
+
# Changes the minimum and maximum number of threads
|
356
|
+
#
|
357
|
+
# @param [Fixnum] min the minimum number of threads
|
358
|
+
# @param [Fixnum] max the maximum number of threads
|
359
|
+
def resize (min, max = nil)
|
360
|
+
@mutex.synchronize do
|
361
|
+
@min = min
|
362
|
+
@max = max || min
|
363
|
+
count = [@tasks_waiting.size,@max].min
|
364
|
+
0.upto(count) do
|
365
|
+
spawn_thread
|
366
|
+
end
|
367
|
+
end
|
368
|
+
trim true
|
369
|
+
end
|
370
|
+
|
371
|
+
# Number of tasks waiting for execution
|
372
|
+
#
|
373
|
+
# @return [Fixnum] the number of tasks
|
374
|
+
def backlog
|
375
|
+
@mutex.synchronize do
|
376
|
+
@tasks_waiting.length
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
# Returns an array of the current waiting and running tasks
|
381
|
+
#
|
382
|
+
# @return [Array<Task>] The tasks
|
383
|
+
def tasks
|
384
|
+
@mutex.synchronize do
|
385
|
+
@tasks_running.dup + @tasks_waiting.dup
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
# Processes the given block as soon as the next thread is available.
|
390
|
+
#
|
391
|
+
# @param [Array] args the block arguments
|
392
|
+
# @yield [*args] the block
|
393
|
+
# @return [Task]
|
394
|
+
def process (*args, &block)
|
395
|
+
process_with_options(nil,*args,&block)
|
396
|
+
end
|
397
|
+
|
398
|
+
# Returns true if a worker thread is currently processing a task
|
399
|
+
# and no work is queued
|
400
|
+
#
|
401
|
+
# @return [Boolean]
|
402
|
+
def process?
|
403
|
+
@mutex.synchronize do
|
404
|
+
waiting != spawned || @tasks_waiting.length > 0
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
# Processes the given block as soon as the next thread is available
|
409
|
+
# with the given options.
|
410
|
+
#
|
411
|
+
# @param (see Task#initialize)
|
412
|
+
# @option (see Task#initialize)
|
413
|
+
# @return [Task]
|
414
|
+
def process_with_options(options,*args, &block)
|
415
|
+
task = Task.new(options,*args, &block)
|
416
|
+
self << task
|
417
|
+
task
|
418
|
+
end
|
419
|
+
|
420
|
+
# Processes the given block from current thread but insures
|
421
|
+
# that during processing no worker thread is executing a task
|
422
|
+
# which has the same sync_key.
|
423
|
+
#
|
424
|
+
# This is useful for instance member calls which are not thread
|
425
|
+
# safe.
|
426
|
+
#
|
427
|
+
# @param [Object] sync_key The sync key
|
428
|
+
# @yield [*args] the code block block
|
429
|
+
# @return [Object] The result of the code block
|
430
|
+
def sync(sync_key,*args,&block)
|
431
|
+
raise ArgumentError,"no sync key" unless sync_key
|
432
|
+
|
433
|
+
@mutex.synchronize do
|
434
|
+
while(!@sync_keys.add?(sync_key))
|
435
|
+
@cond_sync_key.wait @mutex #wait until someone has removed a key
|
436
|
+
end
|
437
|
+
end
|
438
|
+
begin
|
439
|
+
result = block.call(*args)
|
440
|
+
ensure
|
441
|
+
@mutex.synchronize do
|
442
|
+
@sync_keys.delete sync_key
|
443
|
+
end
|
444
|
+
@cond_sync_key.signal
|
445
|
+
@cond.signal # worker threads are just waiting for work no matter if it is
|
446
|
+
# because of a deletion of a sync_key or a task was added
|
447
|
+
end
|
448
|
+
result
|
449
|
+
end
|
450
|
+
|
451
|
+
# Processes the given {Task} as soon as the next thread is available
|
452
|
+
#
|
453
|
+
# @param [Task] task The task.
|
454
|
+
# @return [Task]
|
455
|
+
def <<(task)
|
456
|
+
raise "cannot add task #{task} it is still running" if task.thread
|
457
|
+
task.reset if task.finished?
|
458
|
+
@mutex.synchronize do
|
459
|
+
if shutdown?
|
460
|
+
raise "unable to add work while shutting down"
|
461
|
+
end
|
462
|
+
task.queued_at = Time.now
|
463
|
+
@tasks_waiting << task
|
464
|
+
if @waiting == 0 && @spawned < @max
|
465
|
+
spawn_thread
|
466
|
+
end
|
467
|
+
@cond.signal
|
468
|
+
end
|
469
|
+
task
|
470
|
+
end
|
471
|
+
|
472
|
+
# Trims the number of threads if threads are waiting for work and
|
473
|
+
# the number of spawned threads is higher than the minimum number.
|
474
|
+
#
|
475
|
+
# @param [boolean] force Trim even if no thread is waiting.
|
476
|
+
def trim (force = false)
|
477
|
+
@mutex.synchronize do
|
478
|
+
if (force || @waiting > 0) && @spawned - @trim_requests > @min
|
479
|
+
@trim_requests += 1
|
480
|
+
@cond.signal
|
481
|
+
end
|
482
|
+
end
|
483
|
+
self
|
484
|
+
end
|
485
|
+
|
486
|
+
# Shuts down all threads.
|
487
|
+
#
|
488
|
+
def shutdown()
|
489
|
+
tasks = nil
|
490
|
+
@mutex.synchronize do
|
491
|
+
@shutdown = true
|
492
|
+
end
|
493
|
+
@cond.broadcast
|
494
|
+
end
|
495
|
+
|
496
|
+
|
497
|
+
# Blocks until all threads were terminated.
|
498
|
+
# This does not terminate any thread by itself and will block for ever
|
499
|
+
# if shutdown was not called.
|
500
|
+
def join
|
501
|
+
@workers.first.join until @workers.empty?
|
502
|
+
self
|
503
|
+
end
|
504
|
+
|
505
|
+
# Given code block is called for every task which was
|
506
|
+
# finished even it was terminated.
|
507
|
+
#
|
508
|
+
# This can be used to store the result for an event loop
|
509
|
+
#
|
510
|
+
# @yield [Task] the code block
|
511
|
+
def on_task_finished (&block)
|
512
|
+
@mutex.synchronize do
|
513
|
+
@callback_on_task_finished = block
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
private
|
518
|
+
|
519
|
+
#calculates the moving average
|
520
|
+
def moving_average(current_val,new_val)
|
521
|
+
return new_val if current_val == 0
|
522
|
+
current_val * 0.95 + new_val * 0.05
|
523
|
+
end
|
524
|
+
|
525
|
+
# spawns a worker thread
|
526
|
+
# must be called from a synchronized block
|
527
|
+
def spawn_thread
|
528
|
+
thread = Thread.new do
|
529
|
+
while !shutdown? do
|
530
|
+
current_task = @mutex.synchronize do
|
531
|
+
while !shutdown?
|
532
|
+
task = @tasks_waiting.each_with_index do |t,i|
|
533
|
+
if !t.sync_key || @sync_keys.add?(t.sync_key)
|
534
|
+
@tasks_waiting.delete_at(i)
|
535
|
+
t.pre_execute(self) # block tasks so that no one is using it at the same time
|
536
|
+
@tasks_running << t
|
537
|
+
@avg_wait_time = moving_average(@avg_wait_time,(Time.now-t.queued_at))
|
538
|
+
break t
|
539
|
+
end
|
540
|
+
end
|
541
|
+
break task unless task.is_a? Array
|
542
|
+
|
543
|
+
if @trim_requests > 0
|
544
|
+
@trim_requests -= 1
|
545
|
+
break
|
546
|
+
end
|
547
|
+
@waiting += 1
|
548
|
+
@cond.wait @mutex
|
549
|
+
@waiting -= 1
|
550
|
+
end or break
|
551
|
+
end or break
|
552
|
+
begin
|
553
|
+
current_task.execute
|
554
|
+
rescue Exception => e
|
555
|
+
ThreadPool.report_exception(nil, e)
|
556
|
+
ensure
|
557
|
+
@mutex.synchronize do
|
558
|
+
@tasks_running.delete current_task
|
559
|
+
@sync_keys.delete(current_task.sync_key) if current_task.sync_key
|
560
|
+
@avg_run_time = moving_average(@avg_run_time,(current_task.stopped_at-current_task.started_at))
|
561
|
+
end
|
562
|
+
if current_task.sync_key
|
563
|
+
@cond_sync_key.signal
|
564
|
+
@cond.signal # maybe another thread is waiting for a sync key
|
565
|
+
end
|
566
|
+
current_task.finalize # propagate state after it was deleted from the internal lists
|
567
|
+
@callback_on_task_finished.call(current_task) if @callback_on_task_finished
|
568
|
+
end
|
569
|
+
trim if auto_trim
|
570
|
+
end
|
571
|
+
|
572
|
+
# we do not have to lock here
|
573
|
+
# because spawn_thread must be called from
|
574
|
+
# a synchronized block
|
575
|
+
@spawned -= 1
|
576
|
+
@workers.delete thread
|
577
|
+
end
|
578
|
+
@spawned += 1
|
579
|
+
@workers << thread
|
580
|
+
rescue Exception => e
|
581
|
+
ThreadPool.report_exception(nil, e)
|
582
|
+
end
|
583
|
+
|
584
|
+
def self.report_exception(msg, e)
|
585
|
+
if msg
|
586
|
+
STDERR.puts msg
|
587
|
+
end
|
588
|
+
STDERR.puts e.message
|
589
|
+
STDERR.puts " #{e.backtrace.join("\n ")}"
|
590
|
+
end
|
591
|
+
end
|
592
|
+
end
|
data/test/test_array.rb
CHANGED
data/test/test_dir.rb
CHANGED
data/test/test_enumerable.rb
CHANGED
data/test/test_event_loop.rb
CHANGED
data/test/test_exception.rb
CHANGED
data/test/test_gc.rb
CHANGED
data/test/test_hash.rb
CHANGED
data/test/test_kernel.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'test/test_config'
|
1
|
+
require './test/test_config'
|
2
2
|
require 'flexmock'
|
3
3
|
require 'tempfile'
|
4
4
|
|
@@ -19,7 +19,7 @@ class TC_Kernel < Test::Unit::TestCase
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
name('test')
|
22
|
-
unknown_method
|
22
|
+
unknown_method()
|
23
23
|
end
|
24
24
|
|
25
25
|
def test_validate_options
|
@@ -152,7 +152,7 @@ class TC_Kernel < Test::Unit::TestCase
|
|
152
152
|
end
|
153
153
|
end
|
154
154
|
name('test')
|
155
|
-
unknown_method
|
155
|
+
unknown_method()
|
156
156
|
EOD
|
157
157
|
io.flush
|
158
158
|
|
@@ -294,16 +294,6 @@ class TC_Kernel < Test::Unit::TestCase
|
|
294
294
|
end
|
295
295
|
end
|
296
296
|
|
297
|
-
Utilrb.require_ext('test_swap') do
|
298
|
-
def test_swap
|
299
|
-
obj = Array.new
|
300
|
-
Kernel.swap!(obj, Hash.new)
|
301
|
-
assert_instance_of Hash, obj
|
302
|
-
|
303
|
-
GC.start
|
304
|
-
end
|
305
|
-
end
|
306
|
-
|
307
297
|
def test_poll
|
308
298
|
flexmock(Kernel).should_receive(:sleep).with(2).twice
|
309
299
|
counter = 0
|
data/test/test_misc.rb
CHANGED
data/test/test_models.rb
CHANGED
data/test/test_module.rb
CHANGED