simple_work_queue 0.9.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/.gitignore +4 -0
- data/LICENSE +19 -0
- data/README.rdoc +43 -0
- data/Rakefile +11 -0
- data/lib/simple_work_queue.rb +206 -0
- data/lib/simple_work_queue/version.rb +3 -0
- data/simple_work_queue.gemspec +20 -0
- data/tasks/rdoc.rake +11 -0
- data/tasks/test.rake +13 -0
- data/test.rb +27 -0
- data/test/tc_simple_work_queue.rb +67 -0
- metadata +78 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2009-2010 Miguel Fonseca
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
= Description
|
2
|
+
|
3
|
+
A work queue is designed to coordinate work between a producer and a pool of worker threads.
|
4
|
+
When some task needs to be performed, the producer adds an object containing the task routine to the work queue.
|
5
|
+
Eventually, one of the worker threads removes the object from the work queue and executes the routine.
|
6
|
+
If the work queue is empty, the worker thread will exit, if more jobs are added to the queue additional worker
|
7
|
+
threads will be created up to the configured maximum.
|
8
|
+
|
9
|
+
Work queues are useful for several reasons:
|
10
|
+
* To easily perform tasks asynchronously and concurrently in your application;
|
11
|
+
* To let you focus on the work you actually want to perform without having to worry about the thread creation and management;
|
12
|
+
* To minimize overhead, by reusing previously constructed threads rather than creating new ones;
|
13
|
+
* To bound resource use, by setting a limit on the maximum number of simultaneously executing threads;
|
14
|
+
|
15
|
+
= Requirements
|
16
|
+
|
17
|
+
JRuby
|
18
|
+
|
19
|
+
= Usage
|
20
|
+
|
21
|
+
Install the gem:
|
22
|
+
|
23
|
+
gem install simple_work_queue
|
24
|
+
|
25
|
+
Run the code:
|
26
|
+
|
27
|
+
require 'rubygems'
|
28
|
+
require 'simple_work_queue'
|
29
|
+
wq = SimpleWorkQueue.new
|
30
|
+
wq.enqueue_b { puts "Hello from the SimpleWorkQueue" }
|
31
|
+
wq.join
|
32
|
+
|
33
|
+
Note that you generally want to bound the number of worker threads:
|
34
|
+
|
35
|
+
# Limit the maximum number of simultaneous worker threads
|
36
|
+
SimpleWorkQueue.new(10)
|
37
|
+
|
38
|
+
= History
|
39
|
+
|
40
|
+
SimpleWorkQueue is a fork of WorkQueue by Miguel Fonseca. SimpleWorkQueue removes the time out and bounded task queue.
|
41
|
+
The timeout created issues under the concurrency implementation in JRuby. The time out mechanism was replaced using a
|
42
|
+
non-blocking queue from the java.util.concurrent library. If a worker thread attempts to remove a task from the task queue
|
43
|
+
and it is empty it will exit the worker thread.
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# require 'rubygems'
|
2
|
+
# require 'rake'
|
3
|
+
# require 'rake/clean'
|
4
|
+
require 'lib/simple_work_queue'
|
5
|
+
require 'bundler/gem_tasks'
|
6
|
+
|
7
|
+
# Load all rakefile extensions
|
8
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].each { |ext| load ext }
|
9
|
+
|
10
|
+
# Set default task
|
11
|
+
task :default => ["test:unit"]
|
@@ -0,0 +1,206 @@
|
|
1
|
+
##
|
2
|
+
# = Name
|
3
|
+
# SimpleWorkQueue
|
4
|
+
#
|
5
|
+
# == Description
|
6
|
+
# This file contains an implementation of a work queue structure.
|
7
|
+
#
|
8
|
+
# == Version
|
9
|
+
# 1.0.0
|
10
|
+
#
|
11
|
+
# == Author
|
12
|
+
# Miguel Fonseca <fmmfonseca@gmail.com>
|
13
|
+
# Mark Menard <mark.menard.tny@gmail.com>
|
14
|
+
#
|
15
|
+
# == Copyright
|
16
|
+
# Copyright 2009-2010 Miguel Fonseca
|
17
|
+
# Copyright 2011 Mark Menard
|
18
|
+
#
|
19
|
+
# == License
|
20
|
+
# MIT (see LICENSE file)
|
21
|
+
#
|
22
|
+
|
23
|
+
require 'java'
|
24
|
+
|
25
|
+
##
|
26
|
+
# = SimpleWorkQueue
|
27
|
+
#
|
28
|
+
# == Description
|
29
|
+
# A simple work queue, designed to coordinate work between a producer and a pool of worker threads.
|
30
|
+
# SimpleWorkQueue is a fork of WorkQueue by Miguel Fonseca that has been simplified and made to work
|
31
|
+
# with JRuby using a non-blocking queue from the Java Concurrency framework.
|
32
|
+
#
|
33
|
+
# SimpleWorkQueue unlike WorkQueue does not support time outs (unneeded because SimpleWorkQueue uses
|
34
|
+
# a non-blocking queue), or limits on the number of queued jobs. If you need these features under
|
35
|
+
# JRuby you will need to find a different solution.
|
36
|
+
#
|
37
|
+
# == Usage
|
38
|
+
# wq = SimpleWorkQueue.new
|
39
|
+
# wq.enqueue_b { puts "Hello from the SimpleWorkQueue" }
|
40
|
+
# wq.join
|
41
|
+
#
|
42
|
+
class SimpleWorkQueue
|
43
|
+
|
44
|
+
##
|
45
|
+
# Creates a new work queue with the desired parameters.
|
46
|
+
#
|
47
|
+
# wq = SimpleWorkQueue.new(5)
|
48
|
+
#
|
49
|
+
def initialize(max_threads=nil)
|
50
|
+
self.max_threads = max_threads
|
51
|
+
@threads = []
|
52
|
+
@threads_lock = Mutex.new
|
53
|
+
@tasks = java.util.concurrent.ConcurrentLinkedQueue.new
|
54
|
+
@threads.taint
|
55
|
+
@tasks.taint
|
56
|
+
self.taint
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Returns the maximum number of worker threads.
|
61
|
+
# This value is set upon initialization and cannot be changed afterwards.
|
62
|
+
#
|
63
|
+
# wq = SimpleWorkQueue.new()
|
64
|
+
# wq.max_threads #=> Infinity
|
65
|
+
# wq = SimpleWorkQueue.new(1)
|
66
|
+
# wq.max_threads #=> 1
|
67
|
+
#
|
68
|
+
def max_threads
|
69
|
+
@max_threads
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Returns the current number of worker threads.
|
74
|
+
# This value is just a snapshot, and may change immediately upon returning.
|
75
|
+
#
|
76
|
+
# wq = SimpleWorkQueue.new(10)
|
77
|
+
# wq.cur_threads #=> 0
|
78
|
+
# wq.enqueue_b {}
|
79
|
+
# wq.cur_threads #=> 1
|
80
|
+
#
|
81
|
+
def cur_threads
|
82
|
+
@threads.size
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Returns the current number of queued tasks.
|
87
|
+
# This value is just a snapshot, and may change immediately upon returning.
|
88
|
+
#
|
89
|
+
# wq = SimpleWorkQueue.new(1)
|
90
|
+
# wq.enqueue_b { sleep(1) }
|
91
|
+
# wq.cur_tasks #=> 0
|
92
|
+
# wq.enqueue_b {}
|
93
|
+
# wq.cur_tasks #=> 1
|
94
|
+
#
|
95
|
+
def cur_tasks
|
96
|
+
@tasks.size
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Schedules the given Proc for future execution by a worker thread.
|
101
|
+
# If there is no space left in the queue, waits until space becomes available.
|
102
|
+
#
|
103
|
+
# wq = SimpleWorkQueue.new(1)
|
104
|
+
# wq.enqueue_p(Proc.new {})
|
105
|
+
#
|
106
|
+
def enqueue_p(proc, *args)
|
107
|
+
@tasks.add([proc,args])
|
108
|
+
spawn_thread
|
109
|
+
self
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Schedules the given Block for future execution by a worker thread.
|
114
|
+
# If there is no space left in the queue, waits until space becomes available.
|
115
|
+
#
|
116
|
+
# wq = SimpleWorkQueue.new(1)
|
117
|
+
# wq.enqueue_b {}
|
118
|
+
#
|
119
|
+
def enqueue_b(*args, &block)
|
120
|
+
@tasks.add([block,args])
|
121
|
+
spawn_thread
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
##
|
126
|
+
# Waits until the tasks queue is empty and all worker threads have finished.
|
127
|
+
#
|
128
|
+
# wq = SimpleWorkQueue.new(1)
|
129
|
+
# wq.enqueue_b { sleep(1) }
|
130
|
+
# wq.join
|
131
|
+
#
|
132
|
+
def join
|
133
|
+
cur_threads.times { dismiss_thread }
|
134
|
+
@threads.dup.each { |thread| thread.join if thread }
|
135
|
+
self
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Stops all worker threads immediately, aborting any ongoing tasks.
|
140
|
+
#
|
141
|
+
# wq = SimpleWorkQueue.new(1)
|
142
|
+
# wq.enqueue_b { sleep(1) }
|
143
|
+
# wq.stop
|
144
|
+
#
|
145
|
+
def stop
|
146
|
+
@threads.dup.each { |thread| thread.exit.join }
|
147
|
+
@tasks.clear
|
148
|
+
self
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
##
|
154
|
+
# Sets the maximum number of worker threads.
|
155
|
+
#
|
156
|
+
def max_threads=(value)
|
157
|
+
raise ArgumentError, "the maximum number of threads must be positive" if value and value <= 0
|
158
|
+
@max_threads = value || 1.0/0
|
159
|
+
end
|
160
|
+
|
161
|
+
##
|
162
|
+
# Enrolls a new worker thread.
|
163
|
+
# The request is only carried out if necessary.
|
164
|
+
#
|
165
|
+
def spawn_thread
|
166
|
+
if cur_threads < max_threads and cur_tasks > 0
|
167
|
+
@threads_lock.synchronize {
|
168
|
+
@threads << Thread.new do
|
169
|
+
begin
|
170
|
+
work()
|
171
|
+
ensure
|
172
|
+
@threads_lock.synchronize { @threads.delete(Thread.current) }
|
173
|
+
end
|
174
|
+
end
|
175
|
+
}
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
##
|
180
|
+
# Instructs an idle worker thread to exit.
|
181
|
+
# The request is only carried out if necessary.
|
182
|
+
#
|
183
|
+
def dismiss_thread
|
184
|
+
@tasks << [Proc.new { Thread.exit }, nil] if cur_threads > 0
|
185
|
+
end
|
186
|
+
|
187
|
+
##
|
188
|
+
# Repeatedly process the tasks queue.
|
189
|
+
#
|
190
|
+
def work
|
191
|
+
loop do
|
192
|
+
begin
|
193
|
+
proc, args = @tasks.poll
|
194
|
+
if proc
|
195
|
+
proc.call(*args)
|
196
|
+
else
|
197
|
+
break
|
198
|
+
end
|
199
|
+
rescue Exception
|
200
|
+
# suppress exception
|
201
|
+
end
|
202
|
+
break if cur_threads > max_threads
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "simple_work_queue/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "simple_work_queue"
|
7
|
+
s.version = SimpleWorkQueue::VERSION
|
8
|
+
s.authors = ["Mark Menard"]
|
9
|
+
s.email = ["mark@mjm.net"]
|
10
|
+
s.homepage = "http://www.github.com/MarkMenard/simple_work_queue"
|
11
|
+
s.summary = %q{A simple work queue for JRuby based on work_queue by Miguel Fonseca.}
|
12
|
+
s.description = %q{A simple work queue for JRuby to manage a pool of worker threads.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "simple_work_queue"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
data/tasks/rdoc.rake
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# require 'rake/rdoctask'
|
2
|
+
#
|
3
|
+
# CLEAN.include("doc")
|
4
|
+
#
|
5
|
+
# # For a list of all attributes refer to http://rake.rubyforge.org/classes/Rake/RDocTask.html
|
6
|
+
# Rake::RDocTask.new do |rd|
|
7
|
+
# rd.title = "work_queue-#{SimpleWorkQueue::VERSION} Documentation"
|
8
|
+
# rd.main = "README.rdoc"
|
9
|
+
# rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
10
|
+
# rd.rdoc_dir = "doc"
|
11
|
+
# end
|
data/tasks/test.rake
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
|
3
|
+
namespace(:test) do
|
4
|
+
|
5
|
+
# For a list of all attributes refer to http://rake.rubyforge.org/classes/Rake/TestTask.html
|
6
|
+
Rake::TestTask.new(:unit) do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.test_files = FileList['test/tc_*.rb']
|
9
|
+
t.verbose = true
|
10
|
+
t.warning = true
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
data/test.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'lib/simple_work_queue'
|
2
|
+
|
3
|
+
10.times do |j|
|
4
|
+
wq = SimpleWorkQueue.new(50)
|
5
|
+
2500.times do |i|
|
6
|
+
wq.enqueue_b do
|
7
|
+
puts "!!!!!!!!!!!!!!!!!!!! DONE - Finished run #{j} !!!!!!!!!!!!!!!!!!!!" if i == 2499
|
8
|
+
end
|
9
|
+
end
|
10
|
+
wq.join
|
11
|
+
end
|
12
|
+
|
13
|
+
puts "Finished noop test."
|
14
|
+
|
15
|
+
10.times do |j|
|
16
|
+
wq = SimpleWorkQueue.new(100)
|
17
|
+
500.times do |i|
|
18
|
+
wq.enqueue_b do
|
19
|
+
sleep rand
|
20
|
+
puts "!!!!!!!!!!!!!!!!!!!! DONE - Finished run #{j} !!!!!!!!!!!!!!!!!!!!" if i == 499
|
21
|
+
end
|
22
|
+
end
|
23
|
+
wq.join
|
24
|
+
end
|
25
|
+
|
26
|
+
# Under JRuby 1.6.3 we never get here.
|
27
|
+
puts "*********************** DONE *********************"
|
@@ -0,0 +1,67 @@
|
|
1
|
+
##
|
2
|
+
# = Name
|
3
|
+
# TC_SimpleWorkQueue
|
4
|
+
#
|
5
|
+
# == Description
|
6
|
+
# This file contains unit tests for the SimpleWorkQueue class.
|
7
|
+
#
|
8
|
+
# == Author
|
9
|
+
# Miguel Fonseca <fmmfonseca@gmail.com>
|
10
|
+
#
|
11
|
+
# == Copyright
|
12
|
+
# Copyright 2009-2010 Miguel Fonseca
|
13
|
+
#
|
14
|
+
# == License
|
15
|
+
# MIT (see LICENSE file)
|
16
|
+
|
17
|
+
require 'test/unit'
|
18
|
+
require 'lib/simple_work_queue'
|
19
|
+
|
20
|
+
class TC_SimpleWorkQueue < Test::Unit::TestCase
|
21
|
+
|
22
|
+
# def setup
|
23
|
+
# end
|
24
|
+
|
25
|
+
# def teardown
|
26
|
+
# end
|
27
|
+
|
28
|
+
def test_enqueue
|
29
|
+
s = String.new
|
30
|
+
wq = SimpleWorkQueue.new
|
31
|
+
# using proc
|
32
|
+
wq.enqueue_p(Proc.new { |str| str.replace("Hello #1") }, s)
|
33
|
+
wq.join
|
34
|
+
assert_equal("Hello #1", s)
|
35
|
+
# using block
|
36
|
+
wq.enqueue_b(s) { |str| str.replace("Hello #2") }
|
37
|
+
wq.join
|
38
|
+
assert_equal("Hello #2", s)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_max_threads
|
42
|
+
assert_raise(ArgumentError) { SimpleWorkQueue.new(0) }
|
43
|
+
assert_raise(ArgumentError) { SimpleWorkQueue.new(-1) }
|
44
|
+
wq = SimpleWorkQueue.new(1)
|
45
|
+
assert_equal(0, wq.cur_threads)
|
46
|
+
wq.enqueue_b { sleep(0.01) }
|
47
|
+
assert_equal(1, wq.cur_threads)
|
48
|
+
wq.enqueue_b { sleep(0.01) }
|
49
|
+
assert_equal(1, wq.cur_threads)
|
50
|
+
wq.join
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_stress
|
54
|
+
a = []
|
55
|
+
m = Mutex.new
|
56
|
+
wq = SimpleWorkQueue.new(250)
|
57
|
+
(1..1000).each do
|
58
|
+
wq.enqueue_b do
|
59
|
+
sleep(0.01)
|
60
|
+
m.synchronize { a.push nil }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
wq.join
|
64
|
+
assert_equal(1000, a.size)
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simple_work_queue
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 57
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
- 1
|
10
|
+
version: 0.9.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Mark Menard
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-08-19 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: A simple work queue for JRuby to manage a pool of worker threads.
|
23
|
+
email:
|
24
|
+
- mark@mjm.net
|
25
|
+
executables: []
|
26
|
+
|
27
|
+
extensions: []
|
28
|
+
|
29
|
+
extra_rdoc_files: []
|
30
|
+
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- LICENSE
|
34
|
+
- README.rdoc
|
35
|
+
- Rakefile
|
36
|
+
- lib/simple_work_queue.rb
|
37
|
+
- lib/simple_work_queue/version.rb
|
38
|
+
- simple_work_queue.gemspec
|
39
|
+
- tasks/rdoc.rake
|
40
|
+
- tasks/test.rake
|
41
|
+
- test.rb
|
42
|
+
- test/tc_simple_work_queue.rb
|
43
|
+
has_rdoc: true
|
44
|
+
homepage: http://www.github.com/MarkMenard/simple_work_queue
|
45
|
+
licenses: []
|
46
|
+
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
hash: 3
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project: simple_work_queue
|
73
|
+
rubygems_version: 1.6.2
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: A simple work queue for JRuby based on work_queue by Miguel Fonseca.
|
77
|
+
test_files:
|
78
|
+
- test/tc_simple_work_queue.rb
|