work_queue 1.0.0 → 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/README.rdoc CHANGED
@@ -4,7 +4,7 @@ A work queue is designed to coordinate work between a producer and a pool of wor
4
4
  When some task needs to be performed, the producer adds an object containing the task routine to the work queue.
5
5
  If the work queue is full, the producer will block until a worker thread removes an object from the queue.
6
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 or until the keep-alive time expires.
7
+ If the work queue is empty, a worker thread will block until an object is made available by the producer.
8
8
 
9
9
  Work queues are useful for several reasons:
10
10
  * To easily perform tasks asynchronously and concurrently in your application;
@@ -32,5 +32,3 @@ Note that you generally want to bound the resources used:
32
32
  WorkQueue.new(10)
33
33
  # Limit the maximum number of queued tasks
34
34
  WorkQueue.new(nil,20)
35
- # Limit the maximum time to preserve idle worker threads
36
- WorkQueue.new(nil,nil,10)
data/lib/work_queue.rb CHANGED
@@ -6,20 +6,20 @@
6
6
  # This file contains an implementation of a work queue structure.
7
7
  #
8
8
  # == Version
9
- # 1.0.0
9
+ # 2.0.0
10
10
  #
11
11
  # == Author
12
12
  # Miguel Fonseca <fmmfonseca@gmail.com>
13
13
  #
14
14
  # == Copyright
15
- # Copyright 2009-2010 Miguel Fonseca
15
+ # Copyright 2009-2011 Miguel Fonseca
16
16
  #
17
17
  # == License
18
18
  # MIT (see LICENSE file)
19
19
  #
20
20
 
21
21
  require 'thread'
22
- require 'timeout'
22
+ require 'monitor'
23
23
 
24
24
  ##
25
25
  # = WorkQueue
@@ -33,211 +33,204 @@ require 'timeout'
33
33
  # wq.join
34
34
  #
35
35
  class WorkQueue
36
-
37
- VERSION = "1.0.0"
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, keep_alive=60)
45
- self.max_threads = max_threads
46
- self.max_tasks = max_tasks
47
- self.keep_alive = keep_alive
48
- @threads = []
49
- @threads_lock = Mutex.new
50
- @tasks = max_tasks ? SizedQueue.new(max_tasks) : Queue.new
51
- @threads.taint
52
- @tasks.taint
53
- self.taint
54
- end
55
-
56
- ##
57
- # Returns the maximum number of worker threads.
58
- # This value is set upon initialization and cannot be changed afterwards.
59
- #
60
- # wq = WorkQueue.new()
61
- # wq.max_threads #=> Infinity
62
- # wq = WorkQueue.new(1)
63
- # wq.max_threads #=> 1
64
- #
65
- def max_threads
66
- @max_threads
67
- end
68
-
69
- ##
70
- # Returns the current number of worker threads.
71
- # This value is just a snapshot, and may change immediately upon returning.
72
- #
73
- # wq = WorkQueue.new(10)
74
- # wq.cur_threads #=> 0
75
- # wq.enqueue_b {}
76
- # wq.cur_threads #=> 1
77
- #
78
- def cur_threads
79
- @threads.size
80
- end
81
-
82
- ##
83
- # Returns the maximum number of queued tasks.
84
- # This value is set upon initialization and cannot be changed afterwards.
85
- #
86
- # wq = WorkQueue.new()
87
- # wq.max_tasks #=> Infinity
88
- # wq = WorkQueue.new(nil,1)
89
- # wq.max_tasks #=> 1
90
- #
91
- def max_tasks
92
- @max_tasks
93
- end
94
-
95
- ##
96
- # Returns the current number of queued tasks.
97
- # This value is just a snapshot, and may change immediately upon returning.
98
- #
99
- # wq = WorkQueue.new(1)
100
- # wq.enqueue_b { sleep(1) }
101
- # wq.cur_tasks #=> 0
102
- # wq.enqueue_b {}
103
- # wq.cur_tasks #=> 1
104
- #
105
- def cur_tasks
106
- @tasks.size
107
- end
108
-
109
- ##
110
- # Returns the number of seconds to keep worker threads alive waiting for new tasks.
111
- # This value is set upon initialization and cannot be changed afterwards.
112
- #
113
- # wq = WorkQueue.new()
114
- # wq.keep_alive #=> 60
115
- # wq = WorkQueue.new(nil,nil,1)
116
- # wq.keep_alive #=> 1
117
- #
118
- def keep_alive
119
- @keep_alive
120
- end
121
-
122
- ##
123
- # Schedules the given Proc for future execution by a worker thread.
124
- # If there is no space left in the queue, waits until space becomes available.
125
- #
126
- # wq = WorkQueue.new(1)
127
- # wq.enqueue_p(Proc.new {})
128
- #
129
- def enqueue_p(proc, *args)
130
- @tasks << [proc,args]
131
- spawn_thread
132
- self
133
- end
134
-
135
- ##
136
- # Schedules the given Block for future execution by a worker thread.
137
- # If there is no space left in the queue, waits until space becomes available.
138
- #
139
- # wq = WorkQueue.new(1)
140
- # wq.enqueue_b {}
141
- #
142
- def enqueue_b(*args, &block)
143
- @tasks << [block,args]
144
- spawn_thread
145
- self
146
- end
147
-
148
- ##
149
- # Waits until the tasks queue is empty and all worker threads have finished.
150
- #
151
- # wq = WorkQueue.new(1)
152
- # wq.enqueue_b { sleep(1) }
153
- # wq.join
154
- #
155
- def join
156
- cur_threads.times { dismiss_thread }
157
- @threads.dup.each { |thread| thread.join }
158
- self
159
- end
160
-
161
- ##
162
- # Stops all worker threads immediately, aborting any ongoing tasks.
163
- #
164
- # wq = WorkQueue.new(1)
165
- # wq.enqueue_b { sleep(1) }
166
- # wq.stop
167
- #
168
- def stop
169
- @threads.dup.each { |thread| thread.exit.join }
170
- @tasks.clear
171
- self
172
- end
173
-
174
- private
175
-
176
- ##
177
- # Sets the maximum number of worker threads.
178
- #
179
- def max_threads=(value)
180
- raise ArgumentError, "the maximum number of threads must be positive" if value and value <= 0
181
- @max_threads = value || 1.0/0
182
- end
183
-
184
- ##
185
- # Sets the maximum number of queued tasks.
186
- #
187
- def max_tasks=(value)
188
- raise ArgumentError, "the maximum number of tasks must be positive" if value and value <= 0
189
- @max_tasks = value || 1.0/0
190
- end
191
-
192
- ##
193
- # Sets the maximum time to keep worker threads alive waiting for new tasks.
194
- #
195
- def keep_alive=(value)
196
- raise ArgumentError, "the keep-alive time must be positive" if value and value <= 0
197
- @keep_alive = value || 1.0/0
198
- end
199
-
200
- ##
201
- # Enrolls a new worker thread.
202
- # The request is only carried out if necessary.
203
- #
204
- def spawn_thread
205
- if cur_threads < max_threads and @tasks.num_waiting <= 0 and cur_tasks > 0
206
- @threads_lock.synchronize {
207
- @threads << Thread.new do
208
- begin
209
- work()
210
- ensure
211
- @threads_lock.synchronize { @threads.delete(Thread.current) }
212
- end
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 }
213
142
  end
214
- }
215
143
  end
216
- end
217
-
218
- ##
219
- # Instructs an idle worker thread to exit.
220
- # The request is only carried out if necessary.
221
- #
222
- def dismiss_thread
223
- @tasks << [Proc.new { Thread.exit }, nil] if cur_threads > 0
224
- end
225
-
226
- ##
227
- # Repeatedly process the tasks queue.
228
- #
229
- def work
230
- loop do
231
- begin
232
- proc, args = timeout(keep_alive) { @tasks.pop }
233
- proc.call(*args)
234
- rescue Timeout::Error
235
- break
236
- rescue Exception
237
- # suppress exception
238
- end
239
- break if cur_threads > max_threads
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
+ @threads.dup.each { |thread| thread.exit.join }
154
+ @threads.clear
155
+ @threads_waiting = 0
156
+ @tasks.clear
157
+ @cur_tasks = 0
240
158
  end
241
- end
242
-
159
+
160
+ private
161
+
162
+ ##
163
+ # Generic
164
+ #
165
+ def enqueue(proc, args)
166
+ @tasks.synchronize do
167
+ @task_completed.wait_while { cur_tasks >= max_tasks }
168
+ @tasks << [proc, args]
169
+ @cur_tasks += 1
170
+ @task_enqueued.signal
171
+ spawn_thread
172
+ end
173
+ end
174
+
175
+ ##
176
+ # Sets the maximum number of worker threads.
177
+ #
178
+ def max_threads=(value)
179
+ raise ArgumentError, "the maximum number of threads must be positive" if value and value <= 0
180
+ @max_threads = value || 1.0/0
181
+ end
182
+
183
+ ##
184
+ # Sets the maximum number of queued tasks.
185
+ #
186
+ def max_tasks=(value)
187
+ raise ArgumentError, "the maximum number of tasks must be positive" if value and value <= 0
188
+ @max_tasks = value || 1.0/0
189
+ end
190
+
191
+ ##
192
+ # Enrolls a new worker thread.
193
+ # The request is only carried out if necessary.
194
+ #
195
+ def spawn_thread
196
+ @threads.synchronize do
197
+ if cur_threads < max_threads and @threads_waiting <= 0 and @tasks.size > 0
198
+ @threads << Thread.new do
199
+ begin
200
+ work
201
+ ensure
202
+ @threads.synchronize do
203
+ @threads.delete(Thread.current)
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
210
+
211
+
212
+ ##
213
+ # Repeatedly process the tasks queue.
214
+ #
215
+ def work
216
+ loop do
217
+ begin
218
+ proc, args = @tasks.synchronize do
219
+ @threads_waiting += 1
220
+ @task_enqueued.wait_while { @tasks.size <= 0 }
221
+ @threads_waiting -= 1
222
+ @tasks.shift
223
+ end
224
+ proc.call(*args)
225
+ rescue Exception => e
226
+ # Suppress Exception
227
+ ensure
228
+ @tasks.synchronize do
229
+ @cur_tasks -= 1
230
+ @task_completed.broadcast
231
+ end
232
+ end
233
+ end
234
+ end
235
+
243
236
  end
data/tasks/test.rake CHANGED
@@ -10,13 +10,4 @@ namespace(:test) do
10
10
  t.warning = true
11
11
  end
12
12
 
13
- desc "Run tests on multiple ruby versions"
14
- task(:compatibility) do
15
- versions = %w[1.8.6 1.8.7 1.9.1 jruby]
16
- system <<-CMD
17
- bash -c 'source ~/.rvm/scripts/rvm;
18
- rvm #{versions.join(',')} rake test:unit'
19
- CMD
20
- end
21
-
22
13
  end
@@ -37,6 +37,28 @@ class TC_WorkQueue < Test::Unit::TestCase
37
37
  wq.join
38
38
  assert_equal(s, "Hello #2")
39
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
40
62
 
41
63
  def test_max_threads
42
64
  assert_raise(ArgumentError) { WorkQueue.new(0) }
@@ -64,21 +86,10 @@ class TC_WorkQueue < Test::Unit::TestCase
64
86
  wq.join
65
87
  end
66
88
 
67
- def test_keep_alive
68
- assert_raise(ArgumentError) { WorkQueue.new(nil,nil,0) }
69
- assert_raise(ArgumentError) { WorkQueue.new(nil,nil,-1) }
70
- wq = WorkQueue.new(1,1,0.01)
71
- wq.enqueue_b { sleep(0.01) }
72
- assert_equal(wq.cur_threads, 1)
73
- sleep(0.1)
74
- assert_equal(wq.cur_threads, 0)
75
- wq.join
76
- end
77
-
78
89
  def test_stress
79
90
  a = []
80
91
  m = Mutex.new
81
- wq = WorkQueue.new(100,200,0.1)
92
+ wq = WorkQueue.new(100,200)
82
93
  (1..1000).each do
83
94
  wq.enqueue_b(a,m) { |str,mut|
84
95
  sleep(0.01)
@@ -89,4 +100,12 @@ class TC_WorkQueue < Test::Unit::TestCase
89
100
  assert_equal(a.size, 1000)
90
101
  end
91
102
 
103
+ def test_kill
104
+ s = String.new
105
+ wq = WorkQueue.new
106
+ wq.enqueue_b(s) { |str| sleep 0.1; str.replace("Hello") }
107
+ wq.kill
108
+ assert_equal(s, "")
109
+ end
110
+
92
111
  end
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: work_queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ hash: 13
5
+ prerelease:
6
+ segments:
7
+ - 2
8
+ - 0
9
+ - 1
10
+ version: 2.0.1
5
11
  platform: ruby
6
12
  authors:
7
13
  - Miguel Fonseca
@@ -9,8 +15,7 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2010-02-17 00:00:00 +00:00
13
- default_executable:
18
+ date: 2011-11-21 00:00:00 Z
14
19
  dependencies: []
15
20
 
16
21
  description:
@@ -29,7 +34,6 @@ files:
29
34
  - tasks/test.rake
30
35
  - lib/work_queue.rb
31
36
  - test/tc_work_queue.rb
32
- has_rdoc: true
33
37
  homepage: http://github.com/fmmfonseca/work_queue
34
38
  licenses: []
35
39
 
@@ -42,21 +46,29 @@ rdoc_options:
42
46
  require_paths:
43
47
  - lib
44
48
  required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
45
50
  requirements:
46
51
  - - ">="
47
52
  - !ruby/object:Gem::Version
53
+ hash: 59
54
+ segments:
55
+ - 1
56
+ - 8
57
+ - 6
48
58
  version: 1.8.6
49
- version:
50
59
  required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
51
61
  requirements:
52
62
  - - ">="
53
63
  - !ruby/object:Gem::Version
64
+ hash: 3
65
+ segments:
66
+ - 0
54
67
  version: "0"
55
- version:
56
68
  requirements: []
57
69
 
58
70
  rubyforge_project:
59
- rubygems_version: 1.3.5
71
+ rubygems_version: 1.8.11
60
72
  signing_key:
61
73
  specification_version: 3
62
74
  summary: A tunable work queue, designed to coordinate work between a producer and a pool of worker threads.