work_queue 1.0.0 → 2.0.1

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