dat-worker-pool 0.1.0

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/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.log
3
+ *.rbc
4
+ .rbx/
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
6
+ gem 'pry'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013-Present Collin Redding and Kelly Redding
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # DatWorkerPool
2
+
3
+ A simple thread pool for processing generic 'work'.
4
+
5
+ ## Usage
6
+
7
+ TODO: Write code samples and usage instructions here
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'dat-worker-pool'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install dat-worker-pool
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "dat-worker-pool/version"
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "dat-worker-pool"
8
+ gem.version = DatWorkerPool::VERSION
9
+ gem.authors = ["Collin Redding", "Kelly Redding"]
10
+ gem.email = ["collin.redding@me.com", "kelly@kellyredding.com"]
11
+ gem.description = "A simple thread pool for processing generic 'work'"
12
+ gem.summary = "A simple thread pool for processing generic 'work'"
13
+ gem.homepage = "http://github.com/redding/dat-worker-pool"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency("SystemTimer", ["~> 1.2"])
21
+
22
+ gem.add_development_dependency("assert")
23
+
24
+ end
@@ -0,0 +1,142 @@
1
+ require 'logger'
2
+ require 'system_timer'
3
+ require 'thread'
4
+
5
+ require 'dat-worker-pool/version'
6
+ require 'dat-worker-pool/queue'
7
+ require 'dat-worker-pool/worker'
8
+
9
+ class DatWorkerPool
10
+
11
+ TimeoutError = Class.new(RuntimeError)
12
+
13
+ attr_reader :logger, :spawned
14
+
15
+ def initialize(min = 0, max = 1, debug = false, &do_work_proc)
16
+ @min_workers = min
17
+ @max_workers = max
18
+ @debug = debug
19
+ @logger = Logger.new(@debug)
20
+ @do_work_proc = do_work_proc
21
+
22
+ @queue = Queue.new
23
+ @workers_waiting = WorkersWaiting.new
24
+
25
+ @mutex = Mutex.new
26
+ @workers = []
27
+ @spawned = 0
28
+
29
+ @min_workers.times{ self.spawn_worker }
30
+ end
31
+
32
+ def work_items
33
+ @queue.work_items
34
+ end
35
+
36
+ def waiting
37
+ @workers_waiting.count
38
+ end
39
+
40
+ # Check if all workers are busy before adding the work. When the work is
41
+ # added, a worker will stop waiting (if it was idle). Because of that, we
42
+ # can't reliably check if all workers are busy. We might think all workers are
43
+ # busy because we just woke up a sleeping worker to process this work. Then we
44
+ # would spawn a worker to do nothing.
45
+ def add_work(work_item)
46
+ return if work_item.nil?
47
+ new_worker_needed = all_workers_are_busy?
48
+ @queue.push work_item
49
+ self.spawn_worker if new_worker_needed && havent_reached_max_workers?
50
+ end
51
+
52
+ # Shutdown each worker and then the queue. Shutting down the queue will
53
+ # signal any workers waiting on it to wake up, so they can start shutting
54
+ # down. If a worker is processing work, then it will be joined and allowed to
55
+ # finish.
56
+ # **NOTE** Any work that is left on the queue isn't processed. The controlling
57
+ # application for the worker pool should gracefully handle these items.
58
+ def shutdown(timeout)
59
+ begin
60
+ SystemTimer.timeout(timeout, TimeoutError) do
61
+ @workers.each(&:shutdown)
62
+ @queue.shutdown
63
+
64
+ # use this pattern instead of `each` -- we don't want to call `join` on
65
+ # every worker (especially if they are shutting down on their own), we
66
+ # just want to make sure that any who haven't had a chance to finish
67
+ # get to (this is safe, otherwise you might get a dead thread in the
68
+ # `each`).
69
+ @workers.first.join until @workers.empty?
70
+ end
71
+ rescue TimeoutError => exception
72
+ exception.message.replace "Timed out shutting down the worker pool"
73
+ @debug ? raise(exception) : self.logger.error(exception.message)
74
+ end
75
+ end
76
+
77
+ # public, because workers need to call it for themselves
78
+ def despawn_worker(worker)
79
+ @mutex.synchronize do
80
+ @spawned -= 1
81
+ @workers.delete worker
82
+ end
83
+ end
84
+
85
+ protected
86
+
87
+ def spawn_worker
88
+ @mutex.synchronize do
89
+ worker = Worker.new(self, @queue, @workers_waiting) do |work_item|
90
+ do_work(work_item)
91
+ end
92
+ @workers << worker
93
+ @spawned += 1
94
+ worker
95
+ end
96
+ end
97
+
98
+ def do_work(work_item)
99
+ begin
100
+ @do_work_proc.call(work_item)
101
+ rescue Exception => exception
102
+ self.logger.error "Exception raised while doing work!"
103
+ self.logger.error "#{exception.class}: #{exception.message}"
104
+ self.logger.error exception.backtrace.join("\n")
105
+ end
106
+ end
107
+
108
+ def all_workers_are_busy?
109
+ @workers_waiting.count <= 0
110
+ end
111
+
112
+ def havent_reached_max_workers?
113
+ @mutex.synchronize do
114
+ @spawned < @max_workers
115
+ end
116
+ end
117
+
118
+ class WorkersWaiting
119
+ attr_reader :count
120
+
121
+ def initialize
122
+ @mutex = Mutex.new
123
+ @count = 0
124
+ end
125
+
126
+ def increment
127
+ @mutex.synchronize{ @count += 1 }
128
+ end
129
+
130
+ def decrement
131
+ @mutex.synchronize{ @count -= 1 }
132
+ end
133
+
134
+ end
135
+
136
+ module Logger
137
+ def self.new(debug)
138
+ debug ? ::Logger.new(STDOUT) : ::Logger.new(File.open('/dev/null', 'w'))
139
+ end
140
+ end
141
+
142
+ end
@@ -0,0 +1,50 @@
1
+ require 'thread'
2
+
3
+ class DatWorkerPool
4
+
5
+ class Queue
6
+
7
+ def initialize
8
+ @work_items = []
9
+ @shutdown = false
10
+ @mutex = Mutex.new
11
+ @condition_variable = ConditionVariable.new
12
+ end
13
+
14
+ def work_items
15
+ @mutex.synchronize{ @work_items }
16
+ end
17
+
18
+ # Add the work_item and wake up the first worker (the `signal`) that's
19
+ # waiting (because of `wait_for_work_item`)
20
+ def push(work_item)
21
+ raise "Unable to add work while shutting down" if @shutdown
22
+ @mutex.synchronize do
23
+ @work_items << work_item
24
+ @condition_variable.signal
25
+ end
26
+ end
27
+
28
+ def pop
29
+ @mutex.synchronize{ @work_items.shift }
30
+ end
31
+
32
+ def empty?
33
+ @mutex.synchronize{ @work_items.empty? }
34
+ end
35
+
36
+ # wait to be signaled by `push`
37
+ def wait_for_work_item
38
+ return if @shutdown
39
+ @mutex.synchronize{ @condition_variable.wait(@mutex) }
40
+ end
41
+
42
+ # wake up any workers who are idle (because of `wait_for_work_item`)
43
+ def shutdown
44
+ @shutdown = true
45
+ @mutex.synchronize{ @condition_variable.broadcast }
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,3 @@
1
+ class DatWorkerPool
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,48 @@
1
+ require 'thread'
2
+
3
+ class DatWorkerPool
4
+
5
+ class Worker
6
+
7
+ def initialize(pool, queue, workers_waiting, &block)
8
+ @pool = pool
9
+ @queue = queue
10
+ @workers_waiting = workers_waiting
11
+ @block = block
12
+ @shutdown = false
13
+ @thread = Thread.new{ work_loop }
14
+ end
15
+
16
+ def shutdown
17
+ @shutdown = true
18
+ end
19
+
20
+ def join(*args)
21
+ @thread.join(*args) if @thread
22
+ end
23
+
24
+ protected
25
+
26
+ def work_loop
27
+ loop do
28
+ self.wait_for_work
29
+ break if @shutdown
30
+ @block.call @queue.pop
31
+ end
32
+ ensure
33
+ @pool.despawn_worker(self)
34
+ end
35
+
36
+ # Wait for work to process by checking if the queue is empty.
37
+ def wait_for_work
38
+ while @queue.empty?
39
+ return if @shutdown
40
+ @workers_waiting.increment
41
+ @queue.wait_for_work_item
42
+ @workers_waiting.decrement
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
data/log/.gitkeep ADDED
File without changes
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ # this file is automatically required when you run `assert`
2
+ # put any test helpers here
3
+
4
+ # add the root dir to the load path
5
+ $LOAD_PATH.unshift(File.expand_path("../..", __FILE__))
6
+
7
+ # require pry for debugging (`binding.pry`)
8
+ require 'pry'
9
+
10
+ # TODO: put test helpers here...
@@ -0,0 +1,33 @@
1
+ require 'assert'
2
+ require 'dat-worker-pool'
3
+
4
+ class UseWorkerPoolTests < Assert::Context
5
+
6
+ desc "defining a custom worker pool"
7
+ setup do
8
+ @mutex = Mutex.new
9
+ @results = []
10
+ @work_pool = DatWorkerPool.new(1, 2, !!ENV['DEBUG']) do |work|
11
+ @mutex.synchronize{ @results << (work * 100) }
12
+ end
13
+ end
14
+
15
+ should "be able to add work, have it processed and stop the pool" do
16
+ @work_pool.add_work 1
17
+ @work_pool.add_work 5
18
+ @work_pool.add_work 2
19
+ @work_pool.add_work 4
20
+ @work_pool.add_work 3
21
+
22
+ sleep 0.1 # allow the worker threads to run
23
+
24
+ @work_pool.shutdown(1)
25
+
26
+ assert_includes 100, @results
27
+ assert_includes 200, @results
28
+ assert_includes 300, @results
29
+ assert_includes 400, @results
30
+ assert_includes 500, @results
31
+ end
32
+
33
+ end
@@ -0,0 +1,128 @@
1
+ require 'assert'
2
+ require 'dat-worker-pool'
3
+
4
+ class DatWorkerPool
5
+
6
+ class BaseTests < Assert::Context
7
+ desc "DatWorkerPool"
8
+ setup do
9
+ @work_pool = DatWorkerPool.new{ }
10
+ end
11
+ subject{ @work_pool }
12
+
13
+ should have_readers :logger, :spawned
14
+ should have_imeths :add_work, :shutdown, :despawn_worker
15
+ should have_imeths :work_items, :waiting
16
+
17
+ end
18
+
19
+ class WorkerBehaviorTests < BaseTests
20
+ desc "workers"
21
+ setup do
22
+ @work_pool = DatWorkerPool.new(1, 2, true){|work| sleep(work) }
23
+ end
24
+
25
+ should "be created as needed and only go up to the maximum number allowed" do
26
+ # the minimum should be spawned and waiting
27
+ assert_equal 1, @work_pool.spawned
28
+ assert_equal 1, @work_pool.waiting
29
+
30
+ # the minimum should be spawned, but no longer waiting
31
+ @work_pool.add_work 5
32
+ assert_equal 1, @work_pool.spawned
33
+ assert_equal 0, @work_pool.waiting
34
+
35
+ # an additional worker should be spawned
36
+ @work_pool.add_work 5
37
+ assert_equal 2, @work_pool.spawned
38
+ assert_equal 0, @work_pool.waiting
39
+
40
+ # no additional workers are spawned, the work waits to be processed
41
+ @work_pool.add_work 5
42
+ assert_equal 2, @work_pool.spawned
43
+ assert_equal 0, @work_pool.waiting
44
+ end
45
+
46
+ should "go back to waiting when they finish working" do
47
+ assert_equal 1, @work_pool.spawned
48
+ assert_equal 1, @work_pool.waiting
49
+
50
+ @work_pool.add_work 1
51
+ assert_equal 1, @work_pool.spawned
52
+ assert_equal 0, @work_pool.waiting
53
+
54
+ sleep 1 # allow the worker to run
55
+
56
+ assert_equal 1, @work_pool.spawned
57
+ assert_equal 1, @work_pool.waiting
58
+ end
59
+
60
+ end
61
+
62
+ class AddWorkAndProcessItTests < BaseTests
63
+ desc "add_work and process"
64
+ setup do
65
+ @result = nil
66
+ @work_pool = DatWorkerPool.new(1){|work| @result = (2 / work) }
67
+ end
68
+
69
+ should "have added the work and processed it by calling the passed block" do
70
+ subject.add_work 2
71
+ sleep 0.1 # ensure worker thread get's a chance to run
72
+ assert_equal 1, @result
73
+ end
74
+
75
+ should "swallow exceptions, so workers don't end unexpectedly" do
76
+ subject.add_work 0
77
+ worker = subject.instance_variable_get("@workers").first
78
+ sleep 0.1
79
+
80
+ assert_equal 1, subject.spawned
81
+ assert_equal 1, subject.waiting
82
+ assert worker.instance_variable_get("@thread").alive?
83
+ end
84
+
85
+ end
86
+
87
+ class ShutdownTests < BaseTests
88
+ desc "shutdown"
89
+ setup do
90
+ @mutex = Mutex.new
91
+ @finished = []
92
+ @work_pool = DatWorkerPool.new(1, 2, true) do |work|
93
+ sleep 1
94
+ @mutex.synchronize{ @finished << work }
95
+ end
96
+ @work_pool.add_work 'a'
97
+ @work_pool.add_work 'b'
98
+ @work_pool.add_work 'c'
99
+ end
100
+
101
+ should "allow any work that has been picked up to be processed" do
102
+ # make sure the workers haven't processed any work
103
+ assert_equal [], @finished
104
+
105
+ subject.shutdown(5)
106
+
107
+ # NOTE, the last work shouldn't have been processed, as it wasn't
108
+ # picked up by a worker
109
+ assert_includes 'a', @finished
110
+ assert_includes 'b', @finished
111
+ assert_not_includes 'c', @finished
112
+
113
+ assert_equal 0, subject.spawned
114
+ assert_equal 0, subject.waiting
115
+ assert_includes 'c', subject.work_items
116
+ end
117
+
118
+ should "timeout if the workers take to long to finish" do
119
+ # make sure the workers haven't processed any work
120
+ assert_equal [], @finished
121
+ assert_raises(DatWorkerPool::TimeoutError) do
122
+ subject.shutdown(0.1)
123
+ end
124
+ end
125
+
126
+ end
127
+
128
+ end
@@ -0,0 +1,68 @@
1
+ require 'assert'
2
+ require 'dat-worker-pool/queue'
3
+
4
+ class DatWorkerPool::Queue
5
+
6
+ class BaseTests < Assert::Context
7
+ desc "DatWorkerPool::Queue"
8
+ setup do
9
+ @queue = DatWorkerPool::Queue.new
10
+ end
11
+ subject{ @queue }
12
+
13
+ should have_imeths :work_items, :push, :pop, :empty?
14
+ should have_imeths :wait_for_work_item, :shutdown
15
+
16
+ should "allow pushing work items onto the queue with #push" do
17
+ subject.push 'work'
18
+ assert_equal [ 'work' ], subject.work_items
19
+ end
20
+
21
+ should "raise an exception if trying to push work when shutdown" do
22
+ subject.shutdown
23
+ assert_raises(RuntimeError){ subject.push('work') }
24
+ end
25
+
26
+ should "pop work items off the queue with #pop" do
27
+ subject.push 'work1'
28
+ subject.push 'work2'
29
+
30
+ assert_equal 2, subject.work_items.size
31
+ assert_equal 'work1', subject.pop
32
+ assert_equal 1, subject.work_items.size
33
+ assert_equal 'work2', subject.pop
34
+ assert_equal 0, subject.work_items.size
35
+ end
36
+
37
+ should "return whether the queue is empty or not with #empty?" do
38
+ assert subject.empty?
39
+ subject.push 'work'
40
+ assert_not subject.empty?
41
+ subject.pop
42
+ assert subject.empty?
43
+ end
44
+
45
+ should "sleep a thread with #wait_for_work_item and " \
46
+ "wake it up with #push" do
47
+ thread = Thread.new{ subject.wait_for_work_item }
48
+ thread.join(0.1) # ensure the thread runs enough to start waiting
49
+ subject.push 'work'
50
+ # if this returns nil, then the thread never finished
51
+ assert_not_nil thread.join(1)
52
+ end
53
+
54
+ should "sleep threads with #wait_for_work_item and " \
55
+ "wake them all up with #shutdown" do
56
+ thread1 = Thread.new{ subject.wait_for_work_item }
57
+ thread1.join(0.1) # ensure the thread runs enough to start waiting
58
+ thread2 = Thread.new{ subject.wait_for_work_item }
59
+ thread2.join(0.1) # ensure the thread runs enough to start waiting
60
+ subject.shutdown
61
+ # if these returns nil, then the threads never finished
62
+ assert_not_nil thread1.join(1)
63
+ assert_not_nil thread2.join(1)
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -0,0 +1,31 @@
1
+ require 'assert'
2
+ require 'dat-worker-pool/worker'
3
+
4
+ require 'dat-worker-pool'
5
+ require 'dat-worker-pool/queue'
6
+
7
+ class DatWorkerPool::Worker
8
+
9
+ class BaseTests < Assert::Context
10
+ desc "DatWorkerPool::Worker"
11
+ setup do
12
+ @pool = DatWorkerPool.new{ }
13
+ @queue = DatWorkerPool::Queue.new
14
+ @workers_waiting = DatWorkerPool::WorkersWaiting.new
15
+ @worker = DatWorkerPool::Worker.new(@pool, @queue, @workers_waiting){ }
16
+ end
17
+ subject{ @worker }
18
+
19
+ should have_imeths :shutdown, :join
20
+
21
+ should "trigger exiting it's work loop with #shutdown and " \
22
+ "join it's thread with #join" do
23
+ @queue.shutdown # ensure the thread is not waiting on the queue
24
+ subject.join(0.1) # ensure the thread is looping for work
25
+ subject.shutdown
26
+ assert_not_nil subject.join(1)
27
+ end
28
+
29
+ end
30
+
31
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dat-worker-pool
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Collin Redding
14
+ - Kelly Redding
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2013-07-15 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ prerelease: false
23
+ version_requirements: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 11
29
+ segments:
30
+ - 1
31
+ - 2
32
+ version: "1.2"
33
+ requirement: *id001
34
+ name: SystemTimer
35
+ type: :runtime
36
+ - !ruby/object:Gem::Dependency
37
+ prerelease: false
38
+ version_requirements: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ requirement: *id002
48
+ name: assert
49
+ type: :development
50
+ description: A simple thread pool for processing generic 'work'
51
+ email:
52
+ - collin.redding@me.com
53
+ - kelly@kellyredding.com
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files: []
59
+
60
+ files:
61
+ - .gitignore
62
+ - Gemfile
63
+ - LICENSE.txt
64
+ - README.md
65
+ - Rakefile
66
+ - dat-worker-pool.gemspec
67
+ - lib/dat-worker-pool.rb
68
+ - lib/dat-worker-pool/queue.rb
69
+ - lib/dat-worker-pool/version.rb
70
+ - lib/dat-worker-pool/worker.rb
71
+ - log/.gitkeep
72
+ - test/helper.rb
73
+ - test/system/use_worker_pool_tests.rb
74
+ - test/unit/dat-worker-pool_tests.rb
75
+ - test/unit/queue_tests.rb
76
+ - test/unit/worker_tests.rb
77
+ - tmp/.gitkeep
78
+ homepage: http://github.com/redding/dat-worker-pool
79
+ licenses: []
80
+
81
+ post_install_message:
82
+ rdoc_options: []
83
+
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ requirements: []
105
+
106
+ rubyforge_project:
107
+ rubygems_version: 1.8.15
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: A simple thread pool for processing generic 'work'
111
+ test_files:
112
+ - test/helper.rb
113
+ - test/system/use_worker_pool_tests.rb
114
+ - test/unit/dat-worker-pool_tests.rb
115
+ - test/unit/queue_tests.rb
116
+ - test/unit/worker_tests.rb