pigeon 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/VERSION +1 -1
- data/lib/pigeon.rb +2 -0
- data/lib/pigeon/engine.rb +6 -2
- data/lib/pigeon/launcher.rb +6 -6
- data/lib/pigeon/processor.rb +3 -3
- data/lib/pigeon/queue.rb +76 -75
- data/lib/pigeon/support.rb +52 -4
- data/pigeon.gemspec +6 -7
- data/test/helper.rb +38 -0
- data/test/unit/pigeon_launcher_test.rb +2 -2
- data/test/unit/pigeon_processor_test.rb +47 -41
- data/test/unit/pigeon_queue_test.rb +157 -115
- metadata +3 -5
data/LICENSE
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.0
|
data/lib/pigeon.rb
CHANGED
data/lib/pigeon/engine.rb
CHANGED
@@ -132,8 +132,12 @@ class Pigeon::Engine
|
|
132
132
|
end
|
133
133
|
|
134
134
|
def self.start(options = nil)
|
135
|
-
|
136
|
-
|
135
|
+
logger = self.engine_logger
|
136
|
+
|
137
|
+
pid = Pigeon::Support.daemonize(logger) do
|
138
|
+
launch({
|
139
|
+
:logger => logger
|
140
|
+
}.merge(options || { }))
|
137
141
|
end
|
138
142
|
|
139
143
|
pid_file.create!(pid)
|
data/lib/pigeon/launcher.rb
CHANGED
@@ -3,23 +3,23 @@ require 'optparse'
|
|
3
3
|
class Pigeon::Launcher
|
4
4
|
# == Class Methods ========================================================
|
5
5
|
|
6
|
-
def self.launch(engine,
|
6
|
+
def self.launch(engine = Pigeon::Engine, *arguments)
|
7
|
+
arguments = %w[ start ] if (arguments.empty?)
|
8
|
+
|
9
|
+
new(engine).handle_args(*arguments)
|
7
10
|
end
|
8
11
|
|
9
12
|
# == Instance Methods =====================================================
|
10
13
|
|
11
14
|
def initialize(with_engine = Pigeon::Engine)
|
12
15
|
@engine = with_engine
|
13
|
-
|
16
|
+
|
17
|
+
yield(self) if (block_given?)
|
14
18
|
end
|
15
19
|
|
16
20
|
def handle_args(*args)
|
17
21
|
op = OptionParser.new
|
18
22
|
|
19
|
-
op.on("-s", "--supervise") do
|
20
|
-
@options[:supervise] = true
|
21
|
-
end
|
22
|
-
|
23
23
|
command = op.parse(*args.flatten).first
|
24
24
|
|
25
25
|
begin
|
data/lib/pigeon/processor.rb
CHANGED
@@ -20,7 +20,7 @@ class Pigeon::Processor
|
|
20
20
|
def initialize(queue = nil, context = nil, &filter)
|
21
21
|
@id = Pigeon::Support.unique_id
|
22
22
|
@lock = Mutex.new
|
23
|
-
@filter = filter
|
23
|
+
@filter = filter
|
24
24
|
@context = context
|
25
25
|
|
26
26
|
if (queue)
|
@@ -40,7 +40,7 @@ class Pigeon::Processor
|
|
40
40
|
if (@queue = queue)
|
41
41
|
@claim = lambda do |task|
|
42
42
|
@lock.synchronize do
|
43
|
-
if (!@task and @filter.call(task))
|
43
|
+
if (!@task and (!@filter or @filter.call(task)))
|
44
44
|
@task = queue.claim(task)
|
45
45
|
|
46
46
|
before_task(@task)
|
@@ -59,7 +59,7 @@ class Pigeon::Processor
|
|
59
59
|
# Returns true if the given task would be accepted by the filter defined
|
60
60
|
# for this processor.
|
61
61
|
def accept?(task)
|
62
|
-
@filter.call(task)
|
62
|
+
!@filter or @filter.call(task)
|
63
63
|
end
|
64
64
|
|
65
65
|
# Returns true if a task is currently being processed, false otherwise.
|
data/lib/pigeon/queue.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
|
1
3
|
class Pigeon::Queue
|
2
4
|
# == Constants ============================================================
|
3
5
|
|
@@ -44,15 +46,13 @@ class Pigeon::Queue
|
|
44
46
|
# and order them, so it should take two arguments and return the relative
|
45
47
|
# difference (-1, 0, 1) like Array#sort would work.
|
46
48
|
def initialize(&block)
|
47
|
-
@filter_lock = Mutex.new
|
48
|
-
@observer_lock = Mutex.new
|
49
|
-
|
50
|
-
@claimable_task = { }
|
51
49
|
@filters = self.class.filters.dup
|
50
|
+
@filters.extend(MonitorMixin)
|
52
51
|
@observers = { }
|
52
|
+
@observers.extend(MonitorMixin)
|
53
|
+
@claimable_task = { }
|
53
54
|
@processors = [ ]
|
54
55
|
@next_task = { }
|
55
|
-
@insert_backlog = [ ]
|
56
56
|
|
57
57
|
if (block_given?)
|
58
58
|
@sort_by = block
|
@@ -69,7 +69,7 @@ class Pigeon::Queue
|
|
69
69
|
raise BlockRequired unless (block_given?)
|
70
70
|
|
71
71
|
@sort_by = block
|
72
|
-
@
|
72
|
+
@filters.synchronize do
|
73
73
|
@tasks = Pigeon::SortedArray.new(&@sort_by) + @tasks
|
74
74
|
|
75
75
|
@next_task = { }
|
@@ -82,10 +82,10 @@ class Pigeon::Queue
|
|
82
82
|
def observe(filter_name = nil, &block)
|
83
83
|
raise BlockRequired unless (block_given?)
|
84
84
|
|
85
|
-
@
|
86
|
-
@observers[filter_name] ||= [ ]
|
85
|
+
@observers.synchronize do
|
86
|
+
set = @observers[filter_name] ||= [ ]
|
87
87
|
|
88
|
-
|
88
|
+
set << block
|
89
89
|
end
|
90
90
|
|
91
91
|
task = assign_next_task(filter_name)
|
@@ -95,7 +95,7 @@ class Pigeon::Queue
|
|
95
95
|
# Proc must be passed in, as a block with an identical function will not
|
96
96
|
# be considered equivalent.
|
97
97
|
def remove_observer(filter_name = nil, &block)
|
98
|
-
@
|
98
|
+
@observers.synchronize do
|
99
99
|
set = @observers[filter_name]
|
100
100
|
|
101
101
|
set and set.delete(block)
|
@@ -104,7 +104,7 @@ class Pigeon::Queue
|
|
104
104
|
|
105
105
|
# Adds a processor to the queue and adds an observer claim method.
|
106
106
|
def add_processor(processor, &claim)
|
107
|
-
@
|
107
|
+
@observers.synchronize do
|
108
108
|
@processors << processor
|
109
109
|
end
|
110
110
|
|
@@ -113,7 +113,7 @@ class Pigeon::Queue
|
|
113
113
|
|
114
114
|
# Removes a processor from the queue and removes an observer claim method.
|
115
115
|
def remove_processor(processor, &claim)
|
116
|
-
@
|
116
|
+
@observers.synchronize do
|
117
117
|
@processors.delete(processor)
|
118
118
|
end
|
119
119
|
|
@@ -125,7 +125,7 @@ class Pigeon::Queue
|
|
125
125
|
def filter(filter_name, &block)
|
126
126
|
raise BlockRequired unless (block_given?)
|
127
127
|
|
128
|
-
@
|
128
|
+
@filters.synchronize do
|
129
129
|
@filters[filter_name] = block
|
130
130
|
end
|
131
131
|
|
@@ -141,69 +141,64 @@ class Pigeon::Queue
|
|
141
141
|
# If there is an insert operation already in progress, put this task in
|
142
142
|
# the backlog for subsequent processing.
|
143
143
|
|
144
|
-
|
145
|
-
|
146
|
-
return task
|
144
|
+
Pigeon::Engine.execute_in_main_thread do
|
145
|
+
self.execute_add_task!(task)
|
147
146
|
end
|
148
147
|
|
149
|
-
|
150
|
-
|
151
|
-
while (active_task) do
|
152
|
-
# Set the claimable task flag for this task since it is not yet in the
|
153
|
-
# actual task queue.
|
154
|
-
@claimable_task[active_task] = true
|
155
|
-
|
156
|
-
unless (@observers.empty?)
|
157
|
-
@observer_lock.synchronize do
|
158
|
-
@observers.each do |filter_name, list|
|
159
|
-
# Check if this task matches the filter restrictions, and if it
|
160
|
-
# does then call the observer chain in order.
|
161
|
-
if (@filters[filter_name].call(active_task))
|
162
|
-
@observers[filter_name].each do |proc|
|
163
|
-
case (proc.arity)
|
164
|
-
when 2
|
165
|
-
proc.call(self, active_task)
|
166
|
-
else
|
167
|
-
proc.call(active_task)
|
168
|
-
end
|
148
|
+
task
|
149
|
+
end
|
169
150
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
151
|
+
def execute_add_task!(task)
|
152
|
+
# Set the claimable task flag for this task since it is not yet in the
|
153
|
+
# actual task queue.
|
154
|
+
@claimable_task[task] = true
|
155
|
+
|
156
|
+
unless (@observers.empty?)
|
157
|
+
@observers.synchronize do
|
158
|
+
@observers.each do |filter_name, list|
|
159
|
+
# Check if this task matches the filter restrictions, and if it
|
160
|
+
# does then call the observer chain in order.
|
161
|
+
if (@filters[filter_name].call(task))
|
162
|
+
@observers[filter_name].each do |proc|
|
163
|
+
case (proc.arity)
|
164
|
+
when 2
|
165
|
+
proc.call(self, task)
|
166
|
+
else
|
167
|
+
proc.call(task)
|
174
168
|
end
|
169
|
+
|
170
|
+
# An observer callback has the opportunity to claim a task,
|
171
|
+
# and if it does, the claimable task flag will be false. Loop
|
172
|
+
# only while the task is claimable.
|
173
|
+
break unless (@claimable_task[task])
|
175
174
|
end
|
176
175
|
end
|
177
176
|
end
|
178
177
|
end
|
178
|
+
end
|
179
179
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
180
|
+
# If this task wasn't claimed by an observer then insert it in the
|
181
|
+
# main task queue.
|
182
|
+
if (@claimable_task.delete(task))
|
183
|
+
@filters.synchronize do
|
184
|
+
@tasks << task
|
185
|
+
|
186
|
+
# Update the next task slots for all of the unassigned filters and
|
187
|
+
# trigger observer callbacks as required.
|
188
|
+
@next_task.each do |filter_name, next_task|
|
189
|
+
next if (next_task)
|
185
190
|
|
186
|
-
|
187
|
-
|
188
|
-
@next_task.each do |filter_name, next_task|
|
189
|
-
next if (next_task)
|
190
|
-
|
191
|
-
if (@filters[filter_name].call(active_task))
|
192
|
-
@next_task[filter_name] = active_task
|
193
|
-
end
|
191
|
+
if (@filters[filter_name].call(task))
|
192
|
+
@next_task[filter_name] = task
|
194
193
|
end
|
195
194
|
end
|
196
195
|
end
|
197
|
-
|
198
|
-
active_task = @insert_backlog.shift
|
199
196
|
end
|
200
|
-
|
201
|
-
task
|
202
197
|
end
|
203
198
|
|
204
199
|
# Iterates over each of the tasks in the queue.
|
205
200
|
def each
|
206
|
-
@
|
201
|
+
@filters.synchronize do
|
207
202
|
tasks = @tasks.dup
|
208
203
|
end
|
209
204
|
|
@@ -217,17 +212,21 @@ class Pigeon::Queue
|
|
217
212
|
# can also be used to further restrict the qualifying tasks.
|
218
213
|
def peek(filter_name = nil, &block)
|
219
214
|
if (block_given?)
|
220
|
-
@
|
215
|
+
@filters.synchronize do
|
221
216
|
@tasks.find(&block)
|
222
217
|
end
|
223
|
-
|
218
|
+
elsif (filter_name)
|
224
219
|
@next_task[filter_name] ||= begin
|
225
|
-
@
|
220
|
+
@filters.synchronize do
|
226
221
|
filter_proc = @filters[filter_name]
|
227
222
|
|
228
223
|
filter_proc and @tasks.find(&filter_proc)
|
229
224
|
end
|
230
225
|
end
|
226
|
+
else
|
227
|
+
@filters.synchronize do
|
228
|
+
@tasks.first
|
229
|
+
end
|
231
230
|
end
|
232
231
|
end
|
233
232
|
|
@@ -235,13 +234,13 @@ class Pigeon::Queue
|
|
235
234
|
# only remove tasks matching that filter's conditions. An optional block
|
236
235
|
# can also be used to further restrict the qualifying tasks.
|
237
236
|
def pull(filter_name = nil, &block)
|
238
|
-
|
237
|
+
if (!block_given? and filter_name)
|
239
238
|
block = @filters[filter_name]
|
240
239
|
end
|
241
240
|
|
242
|
-
@
|
243
|
-
tasks = @tasks.select(&block)
|
244
|
-
|
241
|
+
@filters.synchronize do
|
242
|
+
tasks = block ? @tasks.select(&block) : @tasks
|
243
|
+
|
245
244
|
@tasks -= tasks
|
246
245
|
|
247
246
|
@next_task.each do |filter_name, next_task|
|
@@ -260,16 +259,18 @@ class Pigeon::Queue
|
|
260
259
|
# be removed from the queue and must be re-inserted if it is to be scheduled
|
261
260
|
# again.
|
262
261
|
def pop(filter_name = nil, &block)
|
263
|
-
@
|
262
|
+
@filters.synchronize do
|
264
263
|
task =
|
265
264
|
if (block_given?)
|
266
265
|
@tasks.find(&block)
|
267
|
-
|
266
|
+
elsif (filter_name)
|
268
267
|
@next_task[filter_name] || begin
|
269
268
|
filter_proc = @filters[filter_name]
|
270
269
|
|
271
270
|
filter_proc and @tasks.find(&filter_proc)
|
272
271
|
end
|
272
|
+
else
|
273
|
+
@tasks.first
|
273
274
|
end
|
274
275
|
|
275
276
|
if (task)
|
@@ -289,7 +290,7 @@ class Pigeon::Queue
|
|
289
290
|
# Claims a task. This is used to indicate that the task will be processed
|
290
291
|
# without having to be inserted into the queue.
|
291
292
|
def claim(task)
|
292
|
-
@
|
293
|
+
@filters.synchronize do
|
293
294
|
if (@claimable_task[task])
|
294
295
|
@claimable_task[task] = false
|
295
296
|
elsif (@tasks.delete(task))
|
@@ -307,9 +308,9 @@ class Pigeon::Queue
|
|
307
308
|
end
|
308
309
|
|
309
310
|
# Returns true if the task is queued, false otherwise.
|
310
|
-
def
|
311
|
-
@
|
312
|
-
@tasks.
|
311
|
+
def include?(task)
|
312
|
+
@filters.synchronize do
|
313
|
+
@tasks.include?(task)
|
313
314
|
end
|
314
315
|
end
|
315
316
|
|
@@ -318,7 +319,7 @@ class Pigeon::Queue
|
|
318
319
|
# otherwise. An optional block can further restrict qualifying tasks.
|
319
320
|
def empty?(filter_name = nil, &block)
|
320
321
|
if (block_given?)
|
321
|
-
@
|
322
|
+
@filters.synchronize do
|
322
323
|
!@tasks.find(&block)
|
323
324
|
end
|
324
325
|
else
|
@@ -332,7 +333,7 @@ class Pigeon::Queue
|
|
332
333
|
def length(filter_name = nil, &block)
|
333
334
|
filter_proc = @filters[filter_name]
|
334
335
|
|
335
|
-
@
|
336
|
+
@filters.synchronize do
|
336
337
|
filter_proc ? @tasks.count(&filter_proc) : nil
|
337
338
|
end
|
338
339
|
end
|
@@ -341,7 +342,7 @@ class Pigeon::Queue
|
|
341
342
|
|
342
343
|
# Copies the list of queued tasks to a new Array.
|
343
344
|
def to_a
|
344
|
-
@
|
345
|
+
@filters.synchronize do
|
345
346
|
@tasks.dup
|
346
347
|
end
|
347
348
|
end
|
@@ -356,7 +357,7 @@ protected
|
|
356
357
|
return task
|
357
358
|
end
|
358
359
|
|
359
|
-
@
|
360
|
+
@filters.synchronize do
|
360
361
|
@next_task[filter_name] ||= @tasks.find(&filter)
|
361
362
|
end
|
362
363
|
end
|
data/lib/pigeon/support.rb
CHANGED
@@ -4,22 +4,70 @@ module Pigeon::Support
|
|
4
4
|
# Uses the double-fork method to create a fully detached background
|
5
5
|
# process. Returns the process ID of the created process. May throw an
|
6
6
|
# exception if these processes could not be created.
|
7
|
-
def daemonize
|
7
|
+
def daemonize(logger = nil)
|
8
|
+
delay = 10
|
8
9
|
rfd, wfd = IO.pipe
|
9
10
|
|
10
11
|
forked_pid = fork do
|
11
|
-
|
12
|
-
|
12
|
+
rfd.close
|
13
|
+
|
14
|
+
supervisor_pid = fork do
|
15
|
+
relaunch = true
|
16
|
+
|
17
|
+
while (relaunch)
|
18
|
+
daemon_pid = fork do
|
19
|
+
begin
|
20
|
+
yield
|
21
|
+
rescue Object => e
|
22
|
+
if (logger)
|
23
|
+
logger.error("Terminated with Exception: [#{e.class}] #{e}")
|
24
|
+
logger.error(e.backtrace.join("\n"))
|
25
|
+
|
26
|
+
Thread.list.each do |thread|
|
27
|
+
logger.error("Stack trace of current threads")
|
28
|
+
logger.error(thread.inspect)
|
29
|
+
|
30
|
+
if (thread.backtrace)
|
31
|
+
logger.error("\t" + thread.backtrace.join("\n\t"))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
begin
|
39
|
+
Process.wait(daemon_pid)
|
40
|
+
|
41
|
+
# A non-zero exit status indicates some sort of error, so the
|
42
|
+
# process will be relaunched after a short delay.
|
43
|
+
relaunch = ($? != 0)
|
44
|
+
|
45
|
+
rescue Interrupt
|
46
|
+
relaunch = false
|
47
|
+
end
|
48
|
+
|
49
|
+
if (relaunch)
|
50
|
+
logger.info("Will relaunch in %d seconds" % delay)
|
51
|
+
|
52
|
+
sleep(delay)
|
53
|
+
else
|
54
|
+
logger.info("Terminated normally")
|
55
|
+
end
|
56
|
+
end
|
13
57
|
end
|
58
|
+
|
59
|
+
wfd.puts(supervisor_pid)
|
14
60
|
|
15
|
-
wfd.puts daemon_pid
|
16
61
|
wfd.flush
|
17
62
|
wfd.close
|
18
63
|
end
|
19
64
|
|
65
|
+
wfd.close
|
66
|
+
|
20
67
|
Process.wait(forked_pid)
|
21
68
|
|
22
69
|
daemon_pid = rfd.readline
|
70
|
+
rfd.close
|
23
71
|
|
24
72
|
daemon_pid.to_i
|
25
73
|
end
|
data/pigeon.gemspec
CHANGED
@@ -5,15 +5,14 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{pigeon}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.6.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = [
|
12
|
-
s.date = %q{2011-
|
13
|
-
s.default_executable = %q{launcher.example}
|
11
|
+
s.authors = [%q{tadman}]
|
12
|
+
s.date = %q{2011-05-12}
|
14
13
|
s.description = %q{Pigeon is a simple way to get started building an EventMachine engine that's intended to run as a background job.}
|
15
14
|
s.email = %q{github@tadman.ca}
|
16
|
-
s.executables = [
|
15
|
+
s.executables = [%q{launcher.example}]
|
17
16
|
s.extra_rdoc_files = [
|
18
17
|
"LICENSE",
|
19
18
|
"README.rdoc"
|
@@ -53,8 +52,8 @@ Gem::Specification.new do |s|
|
|
53
52
|
"test/unit/pigeon_test.rb"
|
54
53
|
]
|
55
54
|
s.homepage = %q{http://github.com/twg/pigeon}
|
56
|
-
s.require_paths = [
|
57
|
-
s.rubygems_version = %q{1.
|
55
|
+
s.require_paths = [%q{lib}]
|
56
|
+
s.rubygems_version = %q{1.8.1}
|
58
57
|
s.summary = %q{Simple daemonized EventMachine engine framework with plug-in support}
|
59
58
|
s.test_files = [
|
60
59
|
"test/helper.rb",
|
data/test/helper.rb
CHANGED
@@ -34,4 +34,42 @@ class Test::Unit::TestCase
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
37
|
+
|
38
|
+
def engine
|
39
|
+
exception = nil
|
40
|
+
|
41
|
+
@engine_thread = Thread.new do
|
42
|
+
Thread.abort_on_exception = true
|
43
|
+
|
44
|
+
# Create a thread for the engine to run on
|
45
|
+
begin
|
46
|
+
Pigeon::Engine.launch do |new_engine|
|
47
|
+
@engine = new_engine
|
48
|
+
|
49
|
+
Thread.new do
|
50
|
+
# Execute the test code in a separate thread to avoid blocking
|
51
|
+
# the EventMachine loop.
|
52
|
+
begin
|
53
|
+
yield
|
54
|
+
rescue Object => exception
|
55
|
+
ensure
|
56
|
+
begin
|
57
|
+
@engine.terminate
|
58
|
+
rescue Object
|
59
|
+
# Shutting down may trigger an exception from time to time
|
60
|
+
# if the engine itself has failed.
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
rescue Object => exception
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
@engine_thread.join
|
70
|
+
|
71
|
+
if (exception)
|
72
|
+
raise exception
|
73
|
+
end
|
74
|
+
end
|
37
75
|
end
|
@@ -4,10 +4,10 @@ class PigeonLauncherTest < Test::Unit::TestCase
|
|
4
4
|
def test_default_launcher
|
5
5
|
pid = Pigeon::Launcher.launch
|
6
6
|
|
7
|
-
assert pid
|
7
|
+
assert pid, "PID should be returned from launcher call"
|
8
8
|
assert Pigeon::Engine.running?
|
9
9
|
|
10
|
-
Pigeon::
|
10
|
+
Pigeon::Engine.stop
|
11
11
|
|
12
12
|
assert !Pigeon::Engine.running?
|
13
13
|
end
|
@@ -77,72 +77,78 @@ class PigeonProcessorTest < Test::Unit::TestCase
|
|
77
77
|
end
|
78
78
|
|
79
79
|
def test_reassigning_queues
|
80
|
-
|
81
|
-
|
80
|
+
engine do
|
81
|
+
queue_a = Pigeon::Queue.new
|
82
|
+
queue_b = Pigeon::Queue.new
|
82
83
|
|
83
|
-
|
84
|
+
processor = Pigeon::Processor.new(queue_a)
|
84
85
|
|
85
|
-
|
86
|
-
|
86
|
+
task_a = TaggedTask.new(0)
|
87
|
+
assert !task_a.finished?
|
87
88
|
|
88
|
-
|
89
|
+
queue_a << task_a
|
89
90
|
|
90
|
-
|
91
|
-
|
92
|
-
|
91
|
+
assert_eventually(1) do
|
92
|
+
task_a.finished?
|
93
|
+
end
|
93
94
|
|
94
|
-
|
95
|
+
processor.queue = queue_b
|
95
96
|
|
96
|
-
|
97
|
-
|
97
|
+
task_b = TaggedTask.new(1)
|
98
|
+
assert !task_b.finished?
|
98
99
|
|
99
|
-
|
100
|
+
queue_b << task_b
|
100
101
|
|
101
|
-
|
102
|
-
|
103
|
-
|
102
|
+
assert_eventually(1) do
|
103
|
+
task_b.finished?
|
104
|
+
end
|
104
105
|
|
105
|
-
|
106
|
+
task_c = TaggedTask.new(2)
|
106
107
|
|
107
|
-
|
108
|
+
queue_a << task_c
|
108
109
|
|
109
|
-
|
110
|
+
sleep(1)
|
110
111
|
|
111
|
-
|
112
|
+
assert_equal false, task_c.finished?
|
113
|
+
end
|
112
114
|
end
|
113
115
|
|
114
116
|
def test_can_unassign_queue_from_processor
|
115
|
-
|
116
|
-
|
117
|
+
engine do
|
118
|
+
queue = Pigeon::Queue.new
|
119
|
+
processor = Pigeon::Processor.new(queue)
|
117
120
|
|
118
|
-
|
119
|
-
|
121
|
+
assert_equal queue, processor.queue
|
122
|
+
assert_equal [ processor ], queue.processors
|
120
123
|
|
121
|
-
|
124
|
+
processor.queue = nil
|
122
125
|
|
123
|
-
|
124
|
-
|
126
|
+
assert_equal nil, processor.queue
|
127
|
+
assert_equal [ ], queue.processors
|
128
|
+
end
|
125
129
|
end
|
126
130
|
|
127
131
|
def test_multiple_processors
|
128
|
-
|
129
|
-
|
132
|
+
engine do
|
133
|
+
queue = Pigeon::Queue.new
|
134
|
+
count = 10000
|
130
135
|
|
131
|
-
|
132
|
-
|
133
|
-
|
136
|
+
count.times do |n|
|
137
|
+
queue << TaggedTask.new(n)
|
138
|
+
end
|
134
139
|
|
135
|
-
|
140
|
+
assert_equal count, queue.length
|
136
141
|
|
137
|
-
|
138
|
-
|
139
|
-
|
142
|
+
processors = (0..9).to_a.collect do
|
143
|
+
Pigeon::Processor.new(queue)
|
144
|
+
end
|
140
145
|
|
141
|
-
|
142
|
-
|
143
|
-
|
146
|
+
assert_eventually(10) do
|
147
|
+
queue.empty?
|
148
|
+
end
|
144
149
|
|
145
|
-
|
146
|
-
|
150
|
+
assert_equal 0, processors.select(&:task?).length
|
151
|
+
assert_equal 0, queue.length
|
152
|
+
end
|
147
153
|
end
|
148
154
|
end
|
@@ -13,16 +13,6 @@ class PigeonQueueTest < Test::Unit::TestCase
|
|
13
13
|
"<#{@tag}>"
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
17
|
-
def setup
|
18
|
-
@engine = Pigeon::Engine.new
|
19
|
-
|
20
|
-
Pigeon::Engine.register_engine(@engine)
|
21
|
-
end
|
22
|
-
|
23
|
-
def teardown
|
24
|
-
Pigeon::Engine.unregister_engine(@engine)
|
25
|
-
end
|
26
16
|
|
27
17
|
def test_empty_state
|
28
18
|
queue = Pigeon::Queue.new
|
@@ -36,170 +26,222 @@ class PigeonQueueTest < Test::Unit::TestCase
|
|
36
26
|
end
|
37
27
|
|
38
28
|
def test_cycling
|
39
|
-
|
29
|
+
engine do
|
30
|
+
queue = Pigeon::Queue.new
|
40
31
|
|
41
|
-
|
32
|
+
task = Pigeon::Task.new
|
42
33
|
|
43
|
-
|
34
|
+
assert_equal task, queue << task
|
35
|
+
|
36
|
+
assert_eventually do
|
37
|
+
queue.peek == task
|
38
|
+
end
|
44
39
|
|
45
|
-
|
46
|
-
|
47
|
-
|
40
|
+
assert queue.peek
|
41
|
+
assert_equal 1, queue.length
|
42
|
+
assert !queue.empty?
|
48
43
|
|
49
|
-
|
44
|
+
found_task = queue.pop
|
50
45
|
|
51
|
-
|
46
|
+
assert_equal task, found_task
|
52
47
|
|
53
|
-
|
54
|
-
|
48
|
+
assert_equal 0, queue.length
|
49
|
+
assert queue.empty?
|
50
|
+
end
|
55
51
|
end
|
56
52
|
|
57
53
|
def test_filtering
|
58
|
-
|
54
|
+
engine do
|
55
|
+
queue = Pigeon::Queue.new
|
59
56
|
|
60
|
-
|
61
|
-
|
62
|
-
|
57
|
+
tasks = (0..9).to_a.collect do |n|
|
58
|
+
queue << TaggedTask.new(n)
|
59
|
+
end
|
60
|
+
|
61
|
+
assert_eventually do
|
62
|
+
queue.length == 10
|
63
|
+
end
|
63
64
|
|
64
|
-
|
65
|
+
assert_equal (0..9).to_a, tasks.to_a.collect(&:tag)
|
65
66
|
|
66
|
-
|
67
|
+
assert_equal tasks[0], queue.peek
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
|
69
|
+
selected_task = queue.peek do |task|
|
70
|
+
task.tag > 0
|
71
|
+
end
|
71
72
|
|
72
|
-
|
73
|
+
assert_equal tasks[1], selected_task
|
73
74
|
|
74
|
-
|
75
|
-
|
76
|
-
|
75
|
+
queue.filter(:over_7) do |task|
|
76
|
+
task.tag > 7
|
77
|
+
end
|
77
78
|
|
78
|
-
|
79
|
-
|
79
|
+
assert_equal tasks[8], queue.peek(:over_7)
|
80
|
+
assert_equal 2, queue.length(:over_7)
|
80
81
|
|
81
|
-
|
82
|
+
pulled_task = queue.pop(:over_7)
|
82
83
|
|
83
|
-
|
84
|
+
assert_equal 9, queue.length
|
84
85
|
|
85
|
-
|
86
|
-
|
86
|
+
assert_equal tasks[9], queue.peek(:over_7)
|
87
|
+
assert_equal 1, queue.length(:over_7)
|
87
88
|
|
88
|
-
|
89
|
+
queue.pop(:over_7)
|
89
90
|
|
90
|
-
|
91
|
-
|
92
|
-
|
91
|
+
assert_equal nil, queue.peek(:over_7)
|
92
|
+
assert_equal 0, queue.length(:over_7)
|
93
|
+
assert_equal true, queue.empty?(:over_7)
|
93
94
|
|
94
|
-
|
95
|
+
new_task = queue << TaggedTask.new(10)
|
95
96
|
|
96
|
-
|
97
|
-
|
98
|
-
|
97
|
+
assert_eventually do
|
98
|
+
queue.include?(new_task)
|
99
|
+
end
|
100
|
+
|
101
|
+
assert_equal new_task, queue.peek(:over_7)
|
102
|
+
assert_equal 1, queue.length(:over_7)
|
103
|
+
assert_equal false, queue.empty?(:over_7)
|
99
104
|
|
100
|
-
|
105
|
+
queue.claim(new_task)
|
101
106
|
|
102
|
-
|
103
|
-
|
104
|
-
|
107
|
+
assert_equal nil, queue.peek(:over_7)
|
108
|
+
assert_equal 0, queue.length(:over_7)
|
109
|
+
assert_equal true, queue.empty?(:over_7)
|
110
|
+
end
|
105
111
|
end
|
106
112
|
|
107
113
|
def test_observe
|
108
|
-
|
114
|
+
engine do
|
115
|
+
queue = Pigeon::Queue.new
|
109
116
|
|
110
|
-
|
111
|
-
|
112
|
-
|
117
|
+
tasks = (0..9).to_a.collect do |n|
|
118
|
+
queue << TaggedTask.new(n)
|
119
|
+
end
|
113
120
|
|
114
|
-
|
115
|
-
|
116
|
-
|
121
|
+
queue.filter(:odd) do |task|
|
122
|
+
task.tag % 2 == 1
|
123
|
+
end
|
124
|
+
|
125
|
+
assert_eventually(2) do
|
126
|
+
queue.length == 10
|
127
|
+
end
|
117
128
|
|
118
|
-
|
119
|
-
|
129
|
+
assert_equal tasks[1], queue.peek(:odd)
|
130
|
+
assert_equal 5, queue.length(:odd)
|
120
131
|
|
121
|
-
|
132
|
+
assert_equal [ tasks[1], tasks[3], tasks[5], tasks[7], tasks[9] ], queue.pull(:odd)
|
122
133
|
|
123
|
-
|
124
|
-
|
134
|
+
assert_equal 5, queue.length
|
135
|
+
assert_equal 0, queue.length(:odd)
|
125
136
|
|
126
|
-
|
137
|
+
added_odd = nil
|
127
138
|
|
128
|
-
|
129
|
-
|
130
|
-
|
139
|
+
queue.observe(:odd) do |task|
|
140
|
+
added_odd = task
|
141
|
+
end
|
131
142
|
|
132
|
-
|
143
|
+
new_task = queue << TaggedTask.new(10)
|
144
|
+
|
145
|
+
assert_eventually(2) do
|
146
|
+
queue.include?(new_task)
|
147
|
+
end
|
133
148
|
|
134
|
-
|
149
|
+
assert_equal nil, added_odd
|
135
150
|
|
136
|
-
|
151
|
+
odd_1 = queue << TaggedTask.new(11)
|
152
|
+
|
153
|
+
assert_eventually(2) do
|
154
|
+
queue.include?(odd_1)
|
155
|
+
end
|
137
156
|
|
138
|
-
|
157
|
+
assert_equal odd_1, added_odd
|
139
158
|
|
140
|
-
|
141
|
-
|
159
|
+
claimed_task = nil
|
160
|
+
has_run = false
|
142
161
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
162
|
+
queue.observe(:odd) do |task|
|
163
|
+
claimed_task = queue.claim(task)
|
164
|
+
has_run = true
|
165
|
+
end
|
147
166
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
167
|
+
# Observer callbacks are not triggered on existing data, only on new
|
168
|
+
# insertions.
|
169
|
+
assert_equal false, has_run
|
170
|
+
assert_equal nil, claimed_task
|
171
|
+
assert_equal 7, queue.length
|
172
|
+
assert_equal 1, queue.length(:odd)
|
173
|
+
|
174
|
+
new_task = queue << TaggedTask.new(12)
|
175
|
+
|
176
|
+
assert_eventually(2) do
|
177
|
+
queue.include?(new_task)
|
178
|
+
end
|
156
179
|
|
157
|
-
|
158
|
-
|
159
|
-
|
180
|
+
assert_equal nil, claimed_task
|
181
|
+
assert_equal 8, queue.length
|
182
|
+
assert_equal 1, queue.length(:odd)
|
160
183
|
|
161
|
-
|
184
|
+
odd_2 = queue << TaggedTask.new(13)
|
185
|
+
|
186
|
+
assert_eventually(2) do
|
187
|
+
claimed_task == odd_2
|
188
|
+
end
|
162
189
|
|
163
|
-
|
164
|
-
|
165
|
-
|
190
|
+
# Adding a task that matches the filter triggers the callback.
|
191
|
+
assert_equal odd_2, claimed_task
|
192
|
+
assert_equal true, has_run
|
166
193
|
|
167
|
-
|
168
|
-
|
194
|
+
# Clear out all of the odd entries.
|
195
|
+
queue.pull(:odd)
|
169
196
|
|
170
|
-
|
171
|
-
|
197
|
+
claimed_task = nil
|
198
|
+
has_run = false
|
172
199
|
|
173
|
-
|
200
|
+
new_task = queue << TaggedTask.new(14)
|
201
|
+
|
202
|
+
assert_eventually(2) do
|
203
|
+
queue.include?(new_task)
|
204
|
+
end
|
174
205
|
|
175
|
-
|
176
|
-
|
206
|
+
assert_equal nil, claimed_task
|
207
|
+
assert_equal false, has_run
|
177
208
|
|
178
|
-
|
209
|
+
odd_2 = queue << TaggedTask.new(15)
|
210
|
+
|
211
|
+
assert_eventually(2) do
|
212
|
+
odd_2 == claimed_task
|
213
|
+
end
|
179
214
|
|
180
|
-
|
181
|
-
|
215
|
+
assert_equal odd_2, claimed_task
|
216
|
+
assert_equal true, has_run
|
182
217
|
|
183
|
-
|
184
|
-
|
218
|
+
assert_equal 8, queue.length
|
219
|
+
assert_equal 0, queue.length(:odd)
|
220
|
+
end
|
185
221
|
end
|
186
222
|
|
187
223
|
def test_can_add_during_observe
|
188
|
-
|
224
|
+
engine do
|
225
|
+
queue = Pigeon::Queue.new
|
189
226
|
|
190
|
-
|
191
|
-
|
192
|
-
|
227
|
+
queue.observe do |task|
|
228
|
+
if (task.tag < 10)
|
229
|
+
queue.claim(task)
|
193
230
|
|
194
|
-
|
231
|
+
queue << TaggedTask.new(task.tag + 1)
|
232
|
+
end
|
195
233
|
end
|
196
|
-
end
|
197
234
|
|
198
|
-
|
235
|
+
new_task = queue << TaggedTask.new(0)
|
236
|
+
|
237
|
+
assert_eventually(2) do
|
238
|
+
queue.peek
|
239
|
+
end
|
199
240
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
241
|
+
assert queue.peek
|
242
|
+
assert_equal 10, queue.peek.tag
|
243
|
+
assert_equal 1, queue.length
|
244
|
+
assert queue.peek
|
245
|
+
end
|
204
246
|
end
|
205
247
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: pigeon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.6.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- tadman
|
@@ -10,8 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
14
|
-
default_executable: launcher.example
|
13
|
+
date: 2011-05-12 00:00:00 Z
|
15
14
|
dependencies:
|
16
15
|
- !ruby/object:Gem::Dependency
|
17
16
|
name: eventmachine
|
@@ -66,7 +65,6 @@ files:
|
|
66
65
|
- test/unit/pigeon_sorted_array_test.rb
|
67
66
|
- test/unit/pigeon_task_test.rb
|
68
67
|
- test/unit/pigeon_test.rb
|
69
|
-
has_rdoc: true
|
70
68
|
homepage: http://github.com/twg/pigeon
|
71
69
|
licenses: []
|
72
70
|
|
@@ -90,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
88
|
requirements: []
|
91
89
|
|
92
90
|
rubyforge_project:
|
93
|
-
rubygems_version: 1.
|
91
|
+
rubygems_version: 1.8.1
|
94
92
|
signing_key:
|
95
93
|
specification_version: 3
|
96
94
|
summary: Simple daemonized EventMachine engine framework with plug-in support
|