brodock-work_queue 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []