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 +19 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/dat-worker-pool.gemspec +24 -0
- data/lib/dat-worker-pool.rb +142 -0
- data/lib/dat-worker-pool/queue.rb +50 -0
- data/lib/dat-worker-pool/version.rb +3 -0
- data/lib/dat-worker-pool/worker.rb +48 -0
- data/log/.gitkeep +0 -0
- data/test/helper.rb +10 -0
- data/test/system/use_worker_pool_tests.rb +33 -0
- data/test/unit/dat-worker-pool_tests.rb +128 -0
- data/test/unit/queue_tests.rb +68 -0
- data/test/unit/worker_tests.rb +31 -0
- metadata +116 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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,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
|