brodock-work_queue 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/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "bundler", "~> 1.0.0"
10
+ gem "jeweler", "~> 1.6.4"
11
+ gem "rcov"
12
+ end
@@ -0,0 +1,20 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ jeweler (1.6.4)
6
+ bundler (~> 1.0)
7
+ git (>= 1.2.5)
8
+ rake
9
+ rake (0.9.2)
10
+ rcov (0.9.10)
11
+ rcov (0.9.10-java)
12
+
13
+ PLATFORMS
14
+ java
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ bundler (~> 1.0.0)
19
+ jeweler (~> 1.6.4)
20
+ rcov
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.
@@ -0,0 +1,38 @@
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
+ If the work queue is full, the producer will block until a worker thread removes an object from the queue.
6
+ Eventually, one of the worker threads removes the object from the work queue and executes the routine.
7
+ If the work queue is empty, a worker thread will block until an object is made available by the producer.
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
+ = Usage
16
+
17
+ Install the gem:
18
+
19
+ gem install brodock-work_queue
20
+
21
+ Or place this on your Gemfile:
22
+
23
+ gem "brodock-work_queue", :require => "work_queue"
24
+
25
+ Run the code:
26
+
27
+ require 'rubygems'
28
+ require 'work_queue'
29
+ wq = WorkQueue.new
30
+ wq.enqueue_b { puts "Hello from the WorkQueue" }
31
+ wq.join
32
+
33
+ Note that you generally want to bound the resources used:
34
+
35
+ # Limit the maximum number of simultaneous worker threads
36
+ WorkQueue.new(10)
37
+ # Limit the maximum number of queued tasks
38
+ WorkQueue.new(nil,20)
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+ $:.unshift File.dirname(__FILE__)
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+ require 'lib/work_queue'
14
+
15
+ require 'jeweler'
16
+ Jeweler::Tasks.new do |gem|
17
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
18
+ gem.name = "brodock-work_queue"
19
+ gem.version = WorkQueue::VERSION
20
+ gem.homepage = "http://github.com/brodock/brodock-work_queue"
21
+ gem.license = "MIT"
22
+ gem.summary = %Q{A tunable work queue, designed to coordinate work between a producer and a pool of worker threads.}
23
+ gem.description = %Q{A tunable work queue, designed to coordinate work between a producer and a pool of worker threads.}
24
+ gem.email = "brodock@gmail.com"
25
+ gem.authors = ["Miguel Fonseca", "Gabriel Mazetto"]
26
+ gem.required_ruby_version = ">= 1.8.6"
27
+ # dependencies defined in Gemfile
28
+ end
29
+ Jeweler::RubygemsDotOrgTasks.new
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |test|
33
+ test.libs << 'lib' << 'test'
34
+ test.pattern = 'test/**/test_*.rb'
35
+ test.verbose = true
36
+ end
37
+
38
+ require 'rcov/rcovtask'
39
+ Rcov::RcovTask.new do |test|
40
+ test.libs << 'test'
41
+ test.pattern = 'test/**/test_*.rb'
42
+ test.verbose = true
43
+ test.rcov_opts << '--exclude "gems/*"'
44
+ end
45
+
46
+ task :default => :test
47
+
48
+ require 'rake/rdoctask'
49
+ Rake::RDocTask.new do |rdoc|
50
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
51
+
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = "brodock-work_queue #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
@@ -0,0 +1,236 @@
1
+ ##
2
+ # = Name
3
+ # WorkQueue
4
+ #
5
+ # == Description
6
+ # This file contains an implementation of a work queue structure.
7
+ #
8
+ # == Version
9
+ # 2.0.0
10
+ #
11
+ # == Author
12
+ # Miguel Fonseca <fmmfonseca@gmail.com>
13
+ #
14
+ # == Copyright
15
+ # Copyright 2009-2011 Miguel Fonseca
16
+ #
17
+ # == License
18
+ # MIT (see LICENSE file)
19
+ #
20
+
21
+ require 'thread'
22
+ require 'monitor'
23
+
24
+ ##
25
+ # = WorkQueue
26
+ #
27
+ # == Description
28
+ # A tunable work queue, designed to coordinate work between a producer and a pool of worker threads.
29
+ #
30
+ # == Usage
31
+ # wq = WorkQueue.new
32
+ # wq.enqueue_b { puts "Hello from the WorkQueue" }
33
+ # wq.join
34
+ #
35
+ class WorkQueue
36
+
37
+ VERSION = "2.0.1"
38
+
39
+ ##
40
+ # Creates a new work queue with the desired parameters.
41
+ #
42
+ # wq = WorkQueue.new(5,10,20)
43
+ #
44
+ def initialize(max_threads=nil, max_tasks=nil)
45
+ self.max_threads = max_threads
46
+ self.max_tasks = max_tasks
47
+ @threads = Array.new
48
+ @threads.extend(MonitorMixin)
49
+ @threads_waiting = 0
50
+ @tasks = Array.new
51
+ @tasks.extend(MonitorMixin)
52
+ @task_enqueued = @tasks.new_cond
53
+ @task_completed = @tasks.new_cond
54
+ @cur_tasks = 0
55
+ end
56
+
57
+ ##
58
+ # Returns the maximum number of worker threads.
59
+ # This value is set upon initialization and cannot be changed afterwards.
60
+ #
61
+ # wq = WorkQueue.new()
62
+ # wq.max_threads #=> Infinity
63
+ # wq = WorkQueue.new(1)
64
+ # wq.max_threads #=> 1
65
+ #
66
+ def max_threads
67
+ @max_threads
68
+ end
69
+
70
+ ##
71
+ # Returns the current number of worker threads.
72
+ # This value is just a snapshot, and may change immediately upon returning.
73
+ #
74
+ # wq = WorkQueue.new(10)
75
+ # wq.cur_threads #=> 0
76
+ # wq.enqueue_b {}
77
+ # wq.cur_threads #=> 1
78
+ #
79
+ def cur_threads
80
+ @threads.size
81
+ end
82
+
83
+ ##
84
+ # Returns the maximum number of queued tasks.
85
+ # This value is set upon initialization and cannot be changed afterwards.
86
+ #
87
+ # wq = WorkQueue.new()
88
+ # wq.max_tasks #=> Infinity
89
+ # wq = WorkQueue.new(nil,1)
90
+ # wq.max_tasks #=> 1
91
+ #
92
+ def max_tasks
93
+ @max_tasks
94
+ end
95
+
96
+ ##
97
+ # Returns the current number of active tasks.
98
+ # This value is just a snapshot, and may change immediately upon returning.
99
+ #
100
+ # wq = WorkQueue.new(1)
101
+ # wq.enqueue_b { sleep(1) }
102
+ # wq.cur_tasks #=> 0
103
+ # wq.enqueue_b {}
104
+ # wq.cur_tasks #=> 1
105
+ #
106
+ def cur_tasks
107
+ @cur_tasks
108
+ end
109
+
110
+ ##
111
+ # Schedules the given Proc for future execution by a worker thread.
112
+ # If there is no space left in the queue, waits until space becomes available.
113
+ #
114
+ # wq = WorkQueue.new(1)
115
+ # wq.enqueue_p(Proc.new {})
116
+ #
117
+ def enqueue_p(proc, *args)
118
+ enqueue(proc, args)
119
+ end
120
+
121
+ ##
122
+ # Schedules the given Block for future execution by a worker thread.
123
+ # If there is no space left in the queue, waits until space becomes available.
124
+ #
125
+ # wq = WorkQueue.new(1)
126
+ # wq.enqueue_b {}
127
+ #
128
+ def enqueue_b(*args, &block)
129
+ enqueue(block, args)
130
+ end
131
+
132
+ ##
133
+ # Waits until the tasks queue is empty and all worker threads have finished.
134
+ #
135
+ # wq = WorkQueue.new(1)
136
+ # wq.enqueue_b { sleep(1) }
137
+ # wq.join
138
+ #
139
+ def join
140
+ @tasks.synchronize do
141
+ @task_completed.wait_while { cur_tasks > 0 }
142
+ end
143
+ end
144
+
145
+ ##
146
+ # Stops all worker threads immediately, aborting any ongoing tasks.
147
+ #
148
+ # wq = WorkQueue.new(1)
149
+ # wq.enqueue_b { sleep(1) }
150
+ # wq.kill
151
+ #
152
+ def kill
153
+ @tasks.synchronize do
154
+ @threads.dup.each { |thread| thread.exit.join }
155
+ @threds.clear
156
+ @threads_waiting = 0
157
+ @tasks.clear
158
+ @cur_tasks = 0
159
+ end
160
+ end
161
+
162
+ private
163
+
164
+ ##
165
+ # Generic
166
+ #
167
+ def enqueue(proc, args)
168
+ @tasks.synchronize do
169
+ @task_completed.wait_while { cur_tasks >= max_tasks }
170
+ @tasks << [proc, args]
171
+ @cur_tasks += 1
172
+ @task_enqueued.signal
173
+ spawn_thread
174
+ end
175
+ end
176
+
177
+ ##
178
+ # Sets the maximum number of worker threads.
179
+ #
180
+ def max_threads=(value)
181
+ raise ArgumentError, "the maximum number of threads must be positive" if value and value <= 0
182
+ @max_threads = value || 1.0/0
183
+ end
184
+
185
+ ##
186
+ # Sets the maximum number of queued tasks.
187
+ #
188
+ def max_tasks=(value)
189
+ raise ArgumentError, "the maximum number of tasks must be positive" if value and value <= 0
190
+ @max_tasks = value || 1.0/0
191
+ end
192
+
193
+ ##
194
+ # Enrolls a new worker thread.
195
+ # The request is only carried out if necessary.
196
+ #
197
+ def spawn_thread
198
+ @threads.synchronize do
199
+ if cur_threads < max_threads and @threads_waiting <= 0 and @tasks.size > 0
200
+ @threads << Thread.new do
201
+ begin
202
+ work
203
+ ensure
204
+ @threads.synchronize do
205
+ @threads.delete(Thread.current)
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+
214
+ ##
215
+ # Repeatedly process the tasks queue.
216
+ #
217
+ def work
218
+ loop do
219
+ begin
220
+ proc, args = @tasks.synchronize do
221
+ @threads_waiting += 1
222
+ @task_enqueued.wait_while { @tasks.size <= 0 }
223
+ @threads_waiting -= 1
224
+ @tasks.shift
225
+ end
226
+ proc.call(*args)
227
+ ensure
228
+ @tasks.synchronize do
229
+ @cur_tasks -= 1
230
+ @task_completed.broadcast
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ end
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'brodock-work_queue'
15
+
16
+ class Test::Unit::TestCase
17
+ end
@@ -0,0 +1,103 @@
1
+ ##
2
+ # = Name
3
+ # TC_WorkQueue
4
+ #
5
+ # == Description
6
+ # This file contains unit tests for the WorkQueue 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/work_queue'
19
+
20
+ class TestWorkQueue < 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 = WorkQueue.new
31
+ # using proc
32
+ wq.enqueue_p(Proc.new { |str| str.replace("Hello #1") }, s)
33
+ wq.join
34
+ assert_equal(s, "Hello #1")
35
+ # using block
36
+ wq.enqueue_b(s) { |str| str.replace("Hello #2") }
37
+ wq.join
38
+ assert_equal(s, "Hello #2")
39
+ end
40
+
41
+ def test_inner_enqueue
42
+ s = String.new
43
+ wq = WorkQueue.new
44
+ wq.enqueue_b do
45
+ sleep 0.01
46
+ wq.enqueue_b(s) { |str| str.replace("Hello #1") }
47
+ sleep 0.01
48
+ end
49
+ wq.join
50
+ assert_equal(s, "Hello #1")
51
+ end
52
+
53
+ def test_threads_recycle
54
+ wq = WorkQueue.new
55
+ wq.enqueue_b { sleep 0.01 }
56
+ sleep 0.02
57
+ assert_equal(wq.cur_threads, 1)
58
+ wq.enqueue_b { sleep 0.01 }
59
+ assert_equal(wq.cur_threads, 1)
60
+ wq.join
61
+ end
62
+
63
+ def test_max_threads
64
+ assert_raise(ArgumentError) { WorkQueue.new(0) }
65
+ assert_raise(ArgumentError) { WorkQueue.new(-1) }
66
+ wq = WorkQueue.new(1)
67
+ assert_equal(wq.cur_threads, 0)
68
+ wq.enqueue_b { sleep(0.01) }
69
+ assert_equal(wq.cur_threads, 1)
70
+ wq.enqueue_b { sleep(0.01) }
71
+ assert_equal(wq.cur_threads, 1)
72
+ sleep(0.1)
73
+ assert_equal(wq.cur_threads, 1)
74
+ wq.join
75
+ end
76
+
77
+ def test_max_tasks
78
+ assert_raise(ArgumentError) { WorkQueue.new(nil,0) }
79
+ assert_raise(ArgumentError) { WorkQueue.new(nil,-1) }
80
+ wq = WorkQueue.new(1,1)
81
+ wq.enqueue_b { sleep(0.01) }
82
+ wq.enqueue_b { sleep(0.01) }
83
+ assert_equal(wq.cur_tasks, 1)
84
+ wq.enqueue_b { sleep(0.01) }
85
+ assert_equal(wq.cur_tasks, 1)
86
+ wq.join
87
+ end
88
+
89
+ def test_stress
90
+ a = []
91
+ m = Mutex.new
92
+ wq = WorkQueue.new(100,200)
93
+ (1..1000).each do
94
+ wq.enqueue_b(a,m) { |str,mut|
95
+ sleep(0.01)
96
+ mut.synchronize { a.push nil }
97
+ }
98
+ end
99
+ wq.join
100
+ assert_equal(a.size, 1000)
101
+ end
102
+
103
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: brodock-work_queue
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Miguel Fonseca
9
+ - Gabriel Mazetto
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2011-10-05 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bundler
17
+ requirement: &2153041240 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 1.0.0
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *2153041240
26
+ - !ruby/object:Gem::Dependency
27
+ name: jeweler
28
+ requirement: &2153040760 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.6.4
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *2153040760
37
+ - !ruby/object:Gem::Dependency
38
+ name: rcov
39
+ requirement: &2153040280 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *2153040280
48
+ description: A tunable work queue, designed to coordinate work between a producer
49
+ and a pool of worker threads.
50
+ email: brodock@gmail.com
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files:
54
+ - LICENSE
55
+ - README.rdoc
56
+ files:
57
+ - Gemfile
58
+ - Gemfile.lock
59
+ - LICENSE
60
+ - README.rdoc
61
+ - Rakefile
62
+ - lib/work_queue.rb
63
+ - test/helper.rb
64
+ - test/test_work_queue.rb
65
+ homepage: http://github.com/brodock/brodock-work_queue
66
+ licenses:
67
+ - MIT
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 1.8.6
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 1.8.10
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: A tunable work queue, designed to coordinate work between a producer and
90
+ a pool of worker threads.
91
+ test_files: []