backburner 1.8.0 → 1.9.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -4
- data/README.md +70 -14
- data/lib/active_job/queue_adapters/backburner_adapter.rb +9 -2
- data/lib/backburner/cli.rb +1 -1
- data/lib/backburner/enqueue_pool.rb +2 -0
- data/lib/backburner/performable.rb +28 -3
- data/lib/backburner/version.rb +1 -1
- data/lib/backburner/worker.rb +38 -4
- data/lib/backburner/workers/threading.rb +16 -0
- data/lib/backburner/workers/threads_on_fork.rb +1 -0
- metadata +8 -82
- data/.github/workflows/ci.yml +0 -45
- data/.github/workflows/github-release.yml +0 -58
- data/.github/workflows/release.yml +0 -221
- data/.gitignore +0 -17
- data/CONTRIBUTING.md +0 -37
- data/Gemfile +0 -7
- data/Rakefile +0 -17
- data/TODO +0 -4
- data/backburner.gemspec +0 -31
- data/examples/custom.rb +0 -25
- data/examples/demo.rb +0 -60
- data/examples/god.rb +0 -46
- data/examples/hooked.rb +0 -87
- data/examples/retried.rb +0 -31
- data/examples/simple.rb +0 -43
- data/examples/stress.rb +0 -31
- data/test/active_job_adapter_test.rb +0 -24
- data/test/async_proxy_test.rb +0 -36
- data/test/back_burner_test.rb +0 -101
- data/test/connection_test.rb +0 -201
- data/test/fixtures/active_job_app.rb +0 -53
- data/test/fixtures/active_jobs.rb +0 -11
- data/test/fixtures/autoload/test_broken_name_job.rb +0 -4
- data/test/fixtures/autoload/test_broken_no_method_job.rb +0 -4
- data/test/fixtures/autoload/test_broken_require_job.rb +0 -4
- data/test/fixtures/autoload/test_broken_runtime_job.rb +0 -4
- data/test/fixtures/autoload/test_broken_syntax_job.rb +0 -4
- data/test/fixtures/autoload/test_broken_timeout_job.rb +0 -4
- data/test/fixtures/autoload/test_same_name_job.rb +0 -5
- data/test/fixtures/hooked.rb +0 -122
- data/test/fixtures/test_fork_jobs.rb +0 -77
- data/test/fixtures/test_forking_jobs.rb +0 -56
- data/test/fixtures/test_jobs.rb +0 -140
- data/test/fixtures/test_queue_settings.rb +0 -54
- data/test/helpers/templogger.rb +0 -22
- data/test/helpers_test.rb +0 -462
- data/test/hooks_test.rb +0 -112
- data/test/job_test.rb +0 -207
- data/test/logger_test.rb +0 -71
- data/test/performable_test.rb +0 -138
- data/test/queue_test.rb +0 -132
- data/test/test_helper.rb +0 -182
- data/test/worker_test.rb +0 -502
- data/test/workers/forking_worker_test.rb +0 -199
- data/test/workers/simple_worker_test.rb +0 -622
- data/test/workers/threading_worker_test.rb +0 -125
- data/test/workers/threads_on_fork_worker_test.rb +0 -751
|
@@ -1,622 +0,0 @@
|
|
|
1
|
-
require File.expand_path('../../test_helper', __FILE__)
|
|
2
|
-
require File.expand_path('../../fixtures/test_jobs', __FILE__)
|
|
3
|
-
require File.expand_path('../../fixtures/hooked', __FILE__)
|
|
4
|
-
|
|
5
|
-
describe "Backburner::Workers::Simple module" do
|
|
6
|
-
before do
|
|
7
|
-
Backburner.default_queues.clear
|
|
8
|
-
@worker_class = Backburner::Workers::Simple
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
describe "for prepare method" do
|
|
12
|
-
it "should make tube names array always unique to avoid duplication" do
|
|
13
|
-
worker = @worker_class.new(["foo", "demo.test.foo"])
|
|
14
|
-
capture_stdout { worker.prepare }
|
|
15
|
-
assert_equal ["demo.test.foo"], worker.tube_names
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
it "should watch specified tubes" do
|
|
19
|
-
worker = @worker_class.new(["foo", "bar"])
|
|
20
|
-
out = capture_stdout { worker.prepare }
|
|
21
|
-
assert_equal ["demo.test.foo", "demo.test.bar"], worker.tube_names
|
|
22
|
-
assert_same_elements ["demo.test.foo", "demo.test.bar"], worker.connection.tubes.watched.map(&:name)
|
|
23
|
-
assert_match(/demo\.test\.foo/, out)
|
|
24
|
-
end # multiple
|
|
25
|
-
|
|
26
|
-
it "should watch single tube" do
|
|
27
|
-
worker = @worker_class.new("foo")
|
|
28
|
-
out = capture_stdout { worker.prepare }
|
|
29
|
-
assert_equal ["demo.test.foo"], worker.tube_names
|
|
30
|
-
assert_same_elements ["demo.test.foo"], worker.connection.tubes.watched.map(&:name)
|
|
31
|
-
assert_match(/demo\.test\.foo/, out)
|
|
32
|
-
end # single
|
|
33
|
-
|
|
34
|
-
it "should respect default_queues settings" do
|
|
35
|
-
Backburner.default_queues.concat(["foo", "bar"])
|
|
36
|
-
worker = @worker_class.new
|
|
37
|
-
out = capture_stdout { worker.prepare }
|
|
38
|
-
assert_equal ["demo.test.foo", "demo.test.bar"], worker.tube_names
|
|
39
|
-
assert_same_elements ["demo.test.foo", "demo.test.bar"], worker.connection.tubes.watched.map(&:name)
|
|
40
|
-
assert_match(/demo\.test\.foo/, out)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
it "should assign based on all tubes" do
|
|
44
|
-
@worker_class.any_instance.expects(:all_existing_queues).once.returns("bar")
|
|
45
|
-
worker = @worker_class.new
|
|
46
|
-
out = capture_stdout { worker.prepare }
|
|
47
|
-
assert_equal ["demo.test.bar"], worker.tube_names
|
|
48
|
-
assert_same_elements ["demo.test.bar"], worker.connection.tubes.watched.map(&:name)
|
|
49
|
-
assert_match(/demo\.test\.bar/, out)
|
|
50
|
-
end # all assign
|
|
51
|
-
|
|
52
|
-
it "should properly retrieve all tubes" do
|
|
53
|
-
worker = @worker_class.new
|
|
54
|
-
out = capture_stdout { worker.prepare }
|
|
55
|
-
assert_contains worker.tube_names, "demo.test.backburner-jobs"
|
|
56
|
-
assert_contains worker.connection.tubes.watched.map(&:name), "demo.test.backburner-jobs"
|
|
57
|
-
assert_match(/demo\.test\.backburner-jobs/, out)
|
|
58
|
-
end # all read
|
|
59
|
-
end # prepare
|
|
60
|
-
|
|
61
|
-
describe "for work_one_job method" do
|
|
62
|
-
before do
|
|
63
|
-
$worker_test_count = 0
|
|
64
|
-
$worker_success = false
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
it "should work a plain enqueued job" do
|
|
68
|
-
clear_jobs!("foo.bar")
|
|
69
|
-
@worker_class.enqueue TestPlainJob, [1, 2], :queue => "foo.bar"
|
|
70
|
-
silenced(2) do
|
|
71
|
-
worker = @worker_class.new('foo.bar')
|
|
72
|
-
worker.prepare
|
|
73
|
-
worker.work_one_job
|
|
74
|
-
end
|
|
75
|
-
assert_equal 4, $worker_test_count
|
|
76
|
-
end # plain enqueue
|
|
77
|
-
|
|
78
|
-
it "should work an enqueued job" do
|
|
79
|
-
clear_jobs!("foo.bar")
|
|
80
|
-
@worker_class.enqueue TestJob, [1, 2], :queue => "foo.bar"
|
|
81
|
-
silenced(2) do
|
|
82
|
-
worker = @worker_class.new('foo.bar')
|
|
83
|
-
worker.prepare
|
|
84
|
-
worker.work_one_job
|
|
85
|
-
end
|
|
86
|
-
assert_equal 3, $worker_test_count
|
|
87
|
-
end # enqueue
|
|
88
|
-
|
|
89
|
-
it "should fail quietly if there's an argument error" do
|
|
90
|
-
clear_jobs!("foo.bar")
|
|
91
|
-
@worker_class.enqueue TestJob, ["bam", "foo", "bar"], :queue => "foo.bar"
|
|
92
|
-
out = silenced(2) do
|
|
93
|
-
worker = @worker_class.new('foo.bar')
|
|
94
|
-
worker.prepare
|
|
95
|
-
worker.work_one_job
|
|
96
|
-
end
|
|
97
|
-
assert_match(/Exception ArgumentError/, out)
|
|
98
|
-
assert_equal 0, $worker_test_count
|
|
99
|
-
end # fail, argument
|
|
100
|
-
|
|
101
|
-
it "should work an enqueued failing job" do
|
|
102
|
-
# NB: The #bury expectation below leaves the job in the queue (as reserved!)
|
|
103
|
-
# since bury is never actually called on the task. Therefore, clear_jobs!()
|
|
104
|
-
# can't remove it which can break a lot of things depending on the order the
|
|
105
|
-
# tests are run. So we ensure that it's using a unique queue name. Mocha
|
|
106
|
-
# lacks expectations with proxies (where we could actually call bury)
|
|
107
|
-
clear_jobs!('foo.bar.failed')
|
|
108
|
-
@worker_class.enqueue TestFailJob, [1, 2], :queue => 'foo.bar.failed'
|
|
109
|
-
Backburner::Job.any_instance.expects(:bury).once
|
|
110
|
-
out = silenced(2) do
|
|
111
|
-
worker = @worker_class.new('foo.bar.failed')
|
|
112
|
-
worker.prepare
|
|
113
|
-
worker.work_one_job
|
|
114
|
-
end
|
|
115
|
-
assert_match(/Exception RuntimeError/, out)
|
|
116
|
-
assert_equal 0, $worker_test_count
|
|
117
|
-
end # fail, runtime error
|
|
118
|
-
|
|
119
|
-
it "should work an invalid job parsed" do
|
|
120
|
-
Beaneater::Tubes.any_instance.expects(:reserve).returns(stub(:body => "{%$^}", :bury => true))
|
|
121
|
-
out = silenced(2) do
|
|
122
|
-
worker = @worker_class.new('foo.bar')
|
|
123
|
-
worker.prepare
|
|
124
|
-
worker.work_one_job
|
|
125
|
-
end
|
|
126
|
-
assert_match(/Exception Backburner::Job::JobFormatInvalid/, out)
|
|
127
|
-
assert_equal 0, $worker_test_count
|
|
128
|
-
end # fail, runtime error
|
|
129
|
-
|
|
130
|
-
it "should work for an async job" do
|
|
131
|
-
clear_jobs!('foo.bar')
|
|
132
|
-
TestAsyncJob.async(:queue => 'foo.bar').foo(3, 5)
|
|
133
|
-
silenced(2) do
|
|
134
|
-
worker = @worker_class.new('foo.bar')
|
|
135
|
-
worker.prepare
|
|
136
|
-
worker.work_one_job
|
|
137
|
-
end
|
|
138
|
-
assert_equal 15, $worker_test_count
|
|
139
|
-
end # async
|
|
140
|
-
|
|
141
|
-
it "should support retrying jobs and burying" do
|
|
142
|
-
clear_jobs!('foo.bar')
|
|
143
|
-
Backburner.configure { |config| config.max_job_retries = 1; config.retry_delay = 0 }
|
|
144
|
-
@worker_class.enqueue TestRetryJob, ["bam", "foo"], :queue => 'foo.bar'
|
|
145
|
-
out = []
|
|
146
|
-
2.times do
|
|
147
|
-
out << silenced(2) do
|
|
148
|
-
worker = @worker_class.new('foo.bar')
|
|
149
|
-
worker.prepare
|
|
150
|
-
worker.work_one_job
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
assert_match(/attempt 1 of 2, retrying/, out.first)
|
|
154
|
-
assert_match(/Finished TestRetryJob/m, out.last)
|
|
155
|
-
assert_match(/attempt 2 of 2, burying/m, out.last)
|
|
156
|
-
assert_equal 2, $worker_test_count
|
|
157
|
-
assert_equal false, $worker_success
|
|
158
|
-
end # retry, bury
|
|
159
|
-
|
|
160
|
-
it "should support retrying jobs and succeeds" do
|
|
161
|
-
clear_jobs!('foo.bar')
|
|
162
|
-
Backburner.configure { |config| config.max_job_retries = 2; config.retry_delay = 0 }
|
|
163
|
-
@worker_class.enqueue TestRetryJob, ["bam", "foo"], :queue => 'foo.bar'
|
|
164
|
-
out = []
|
|
165
|
-
3.times do
|
|
166
|
-
out << silenced(2) do
|
|
167
|
-
worker = @worker_class.new('foo.bar')
|
|
168
|
-
worker.prepare
|
|
169
|
-
worker.work_one_job
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
assert_match(/attempt 1 of 3, retrying/, out.first)
|
|
173
|
-
assert_match(/attempt 2 of 3, retrying/, out[1])
|
|
174
|
-
assert_match(/Completed TestRetryJob/m, out.last)
|
|
175
|
-
refute_match(/failed/, out.last)
|
|
176
|
-
assert_equal 3, $worker_test_count
|
|
177
|
-
assert_equal true, $worker_success
|
|
178
|
-
end # retrying, succeeds
|
|
179
|
-
|
|
180
|
-
it "should back off retries exponentially" do
|
|
181
|
-
max_job_retries = 3
|
|
182
|
-
clear_jobs!('foo.bar')
|
|
183
|
-
Backburner.configure do |config|
|
|
184
|
-
config.max_job_retries = max_job_retries
|
|
185
|
-
config.retry_delay = 0
|
|
186
|
-
#config.retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) } # default retry_delay_proc
|
|
187
|
-
end
|
|
188
|
-
@worker_class.enqueue TestConfigurableRetryJob, [max_job_retries], :queue => 'foo.bar'
|
|
189
|
-
out = []
|
|
190
|
-
(max_job_retries + 1).times do
|
|
191
|
-
out << silenced(10) do
|
|
192
|
-
worker = @worker_class.new('foo.bar')
|
|
193
|
-
worker.prepare
|
|
194
|
-
worker.work_one_job
|
|
195
|
-
end
|
|
196
|
-
end
|
|
197
|
-
assert_match(/attempt 1 of 4, retrying in 0/, out.first)
|
|
198
|
-
assert_match(/attempt 2 of 4, retrying in 1/, out[1])
|
|
199
|
-
assert_match(/attempt 3 of 4, retrying in 8/, out[2])
|
|
200
|
-
assert_match(/Completed TestConfigurableRetryJob/m, out.last)
|
|
201
|
-
refute_match(/failed/, out.last)
|
|
202
|
-
assert_equal 4, $worker_test_count
|
|
203
|
-
assert_equal true, $worker_success
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
it "should allow configurable back off retry delays" do
|
|
207
|
-
max_job_retries = 3
|
|
208
|
-
clear_jobs!('foo.bar')
|
|
209
|
-
Backburner.configure do |config|
|
|
210
|
-
config.max_job_retries = max_job_retries
|
|
211
|
-
config.retry_delay = 0
|
|
212
|
-
config.retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 2) }
|
|
213
|
-
end
|
|
214
|
-
@worker_class.enqueue TestConfigurableRetryJob, [max_job_retries], :queue => 'foo.bar'
|
|
215
|
-
out = []
|
|
216
|
-
(max_job_retries + 1).times do
|
|
217
|
-
out << silenced(5) do
|
|
218
|
-
worker = @worker_class.new('foo.bar')
|
|
219
|
-
worker.prepare
|
|
220
|
-
worker.work_one_job
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
assert_match(/attempt 1 of 4, retrying in 0/, out.first)
|
|
224
|
-
assert_match(/attempt 2 of 4, retrying in 1/, out[1])
|
|
225
|
-
assert_match(/attempt 3 of 4, retrying in 4/, out[2])
|
|
226
|
-
assert_match(/Completed TestConfigurableRetryJob/m, out.last)
|
|
227
|
-
refute_match(/failed/, out.last)
|
|
228
|
-
assert_equal 4, $worker_test_count
|
|
229
|
-
assert_equal true, $worker_success
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
it "should allow queue override of retries" do
|
|
233
|
-
max_job_retries = TestRetryWithQueueOverridesJob.queue_max_job_retries
|
|
234
|
-
clear_jobs!('foo.bar')
|
|
235
|
-
Backburner.configure do |config|
|
|
236
|
-
# Config should be overridden by queue overrides
|
|
237
|
-
config.max_job_retries = 20
|
|
238
|
-
config.retry_delay = 60
|
|
239
|
-
#config.retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) } # default retry_delay_proc
|
|
240
|
-
end
|
|
241
|
-
@worker_class.enqueue TestRetryWithQueueOverridesJob, [max_job_retries], :queue => 'foo.bar'
|
|
242
|
-
out = []
|
|
243
|
-
(max_job_retries + 1).times do
|
|
244
|
-
out << silenced(5) do
|
|
245
|
-
worker = @worker_class.new('foo.bar')
|
|
246
|
-
worker.prepare
|
|
247
|
-
worker.work_one_job
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
assert_match(/attempt 1 of 4, retrying in 0/, out.first)
|
|
251
|
-
assert_match(/attempt 2 of 4, retrying in 1/, out[1])
|
|
252
|
-
assert_match(/attempt 3 of 4, retrying in 4/, out[2])
|
|
253
|
-
assert_match(/Completed TestRetryWithQueueOverridesJob/m, out.last)
|
|
254
|
-
refute_match(/failed/, out.last)
|
|
255
|
-
assert_equal 4, $worker_test_count
|
|
256
|
-
assert_equal true, $worker_success
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
it "should round a fractional retry delay up to whole seconds" do
|
|
260
|
-
conn = beanstalk_connection
|
|
261
|
-
default_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) }
|
|
262
|
-
[
|
|
263
|
-
[TestConfigurableRetryJob, 5, lambda { |min_retry_delay, num_retries| 1.5 }, 2], # a retry_delay_proc returning a Float
|
|
264
|
-
[TestConfigurableRetryJob, 0.5, default_proc, 1], # a Float retry_delay
|
|
265
|
-
[TestFloatRetryDelayJob, 5, default_proc, 2] # a class's Float queue_retry_delay (1.5)
|
|
266
|
-
].each do |job_class, retry_delay, retry_delay_proc, expected|
|
|
267
|
-
clear_jobs!('foo.bar')
|
|
268
|
-
Backburner.configure do |config|
|
|
269
|
-
config.max_job_retries = 1
|
|
270
|
-
config.retry_delay = retry_delay
|
|
271
|
-
config.retry_delay_proc = retry_delay_proc
|
|
272
|
-
end
|
|
273
|
-
id = @worker_class.enqueue(job_class, [5], :queue => 'foo.bar')[:id]
|
|
274
|
-
out = silenced(5) do
|
|
275
|
-
worker = @worker_class.new('foo.bar')
|
|
276
|
-
worker.prepare
|
|
277
|
-
worker.work_one_job
|
|
278
|
-
end
|
|
279
|
-
assert_match(/attempt 1 of 2, retrying in #{expected}s/, out)
|
|
280
|
-
stats = conn.jobs.find(id).stats
|
|
281
|
-
assert_equal [expected, 'delayed'], [stats.delay, stats.state], "#{job_class} with retry_delay #{retry_delay}"
|
|
282
|
-
end
|
|
283
|
-
ensure
|
|
284
|
-
conn.close if conn
|
|
285
|
-
clear_jobs!('foo.bar')
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
it "should support event hooks without retry" do
|
|
289
|
-
$hooked_fail_count = 0
|
|
290
|
-
clear_jobs!('foo.bar.events')
|
|
291
|
-
out = silenced(2) do
|
|
292
|
-
HookedObjectSuccess.async(:queue => 'foo.bar.events').foo(5)
|
|
293
|
-
worker = @worker_class.new('foo.bar.events')
|
|
294
|
-
worker.prepare
|
|
295
|
-
worker.work_one_job
|
|
296
|
-
end
|
|
297
|
-
assert_match(/before_enqueue.*after_enqueue.*Working 1 queues/m, out)
|
|
298
|
-
assert_match(/!!before_enqueue_bar!! \[nil, :foo, 5\]/, out)
|
|
299
|
-
assert_match(/!!after_enqueue_bar!! \[nil, :foo, 5\]/, out)
|
|
300
|
-
assert_match(/!!before_perform_foo!! \[nil, "foo", 5\]/, out)
|
|
301
|
-
assert_match(/!!BEGIN around_perform_bar!! \[nil, "foo", 5\]/, out)
|
|
302
|
-
assert_match(/!!BEGIN around_perform_cat!! \[nil, "foo", 5\]/, out)
|
|
303
|
-
assert_match(/!!on_failure_foo!!.*HookFailError/, out)
|
|
304
|
-
assert_match(/!!on_bury_foo!! \[nil, "foo", 5\]/, out)
|
|
305
|
-
assert_match(/attempt 1 of 1, burying/, out)
|
|
306
|
-
end # event hooks, no retry
|
|
307
|
-
|
|
308
|
-
it "should support event hooks with retry" do
|
|
309
|
-
$hooked_fail_count = 0
|
|
310
|
-
clear_jobs!('foo.bar.events.retry')
|
|
311
|
-
Backburner.configure { |config| config.max_job_retries = 1; config.retry_delay = 0 }
|
|
312
|
-
out = silenced(2) do
|
|
313
|
-
HookedObjectSuccess.async(:queue => 'foo.bar.events.retry').foo(5)
|
|
314
|
-
worker = @worker_class.new('foo.bar.events.retry')
|
|
315
|
-
worker.prepare
|
|
316
|
-
2.times do
|
|
317
|
-
worker.work_one_job
|
|
318
|
-
end
|
|
319
|
-
end
|
|
320
|
-
assert_match(/before_enqueue.*after_enqueue.*Working 1 queues/m, out)
|
|
321
|
-
assert_match(/!!before_enqueue_bar!! \[nil, :foo, 5\]/, out)
|
|
322
|
-
assert_match(/!!after_enqueue_bar!! \[nil, :foo, 5\]/, out)
|
|
323
|
-
assert_match(/!!before_perform_foo!! \[nil, "foo", 5\]/, out)
|
|
324
|
-
assert_match(/!!BEGIN around_perform_bar!! \[nil, "foo", 5\]/, out)
|
|
325
|
-
assert_match(/!!BEGIN around_perform_cat!! \[nil, "foo", 5\]/, out)
|
|
326
|
-
assert_match(/!!on_failure_foo!!.*HookFailError/, out)
|
|
327
|
-
assert_match(/!!on_failure_foo!!.*retrying.*around_perform_bar.*around_perform_cat/m, out)
|
|
328
|
-
assert_match(/!!on_retry_foo!! 1 0 \[nil, "foo", 5\]/, out)
|
|
329
|
-
assert_match(/attempt 1 of 2, retrying/, out)
|
|
330
|
-
assert_match(/!!before_perform_foo!! \[nil, "foo", 5\]/, out)
|
|
331
|
-
assert_match(/!!END around_perform_bar!! \[nil, "foo", 5\]/, out)
|
|
332
|
-
assert_match(/!!END around_perform_cat!! \[nil, "foo", 5\]/, out)
|
|
333
|
-
assert_match(/!!after_perform_foo!! \[nil, "foo", 5\]/, out)
|
|
334
|
-
assert_match(/Finished HookedObjectSuccess/, out)
|
|
335
|
-
end # event hooks, with retry
|
|
336
|
-
|
|
337
|
-
it "should support event hooks with stopping enqueue" do
|
|
338
|
-
$hooked_fail_count = 0
|
|
339
|
-
worker = @worker_class.new('foo.bar.events.retry2')
|
|
340
|
-
clear_jobs!('foo.bar.events.retry2')
|
|
341
|
-
silenced(2) do
|
|
342
|
-
HookedObjectBeforeEnqueueFail.async(:queue => 'foo.bar.events.retry2').foo(5)
|
|
343
|
-
end
|
|
344
|
-
expanded_tube = [Backburner.configuration.tube_namespace, 'foo.bar.events.retry2'].join(".")
|
|
345
|
-
assert_nil worker.connection.tubes[expanded_tube].peek(:ready)
|
|
346
|
-
end # stopping enqueue
|
|
347
|
-
|
|
348
|
-
it "should support event hooks with stopping perform" do
|
|
349
|
-
$hooked_fail_count = 0
|
|
350
|
-
clear_jobs!('foo.bar.events.retry3')
|
|
351
|
-
[Backburner.configuration.tube_namespace, 'foo.bar.events.retry3'].join(".")
|
|
352
|
-
out = silenced(2) do
|
|
353
|
-
HookedObjectBeforePerformFail.async(:queue => 'foo.bar.events.retry3').foo(10)
|
|
354
|
-
worker = @worker_class.new('foo.bar.events.retry3')
|
|
355
|
-
worker.prepare
|
|
356
|
-
worker.work_one_job
|
|
357
|
-
end
|
|
358
|
-
assert_match(/!!before_perform_foo!! \[nil, "foo", 10\]/, out)
|
|
359
|
-
assert_match(/before_perform_foo.*Completed/m, out)
|
|
360
|
-
refute_match(/Fail ran!!/, out)
|
|
361
|
-
refute_match(/HookFailError/, out)
|
|
362
|
-
end # stopping perform
|
|
363
|
-
|
|
364
|
-
it "should delete a job whose before_perform hook stopped it" do
|
|
365
|
-
clear_jobs!('foo.bar.events.retry3')
|
|
366
|
-
worker = @worker_class.new('foo.bar.events.retry3')
|
|
367
|
-
silenced(2) do
|
|
368
|
-
HookedObjectBeforePerformFail.async(:queue => 'foo.bar.events.retry3').foo(10)
|
|
369
|
-
worker.prepare
|
|
370
|
-
worker.work_one_job
|
|
371
|
-
end
|
|
372
|
-
stats = worker.connection.tubes['demo.test.foo.bar.events.retry3'].stats
|
|
373
|
-
assert_equal 0, stats.current_jobs_reserved, "the aborted job is still reserved"
|
|
374
|
-
assert_equal 0, stats.current_jobs_ready + stats.current_jobs_delayed + stats.current_jobs_buried
|
|
375
|
-
end # stopping perform deletes
|
|
376
|
-
|
|
377
|
-
it "should keep working when the TTR ran out before the job could be buried" do
|
|
378
|
-
clear_jobs!("foo.bar.ttr")
|
|
379
|
-
@worker_class.enqueue TestOverTTRJob, [], :queue => "foo.bar.ttr"
|
|
380
|
-
worker = @worker_class.new('foo.bar.ttr')
|
|
381
|
-
out = silenced(6) do
|
|
382
|
-
worker.prepare
|
|
383
|
-
worker.work_one_job
|
|
384
|
-
end
|
|
385
|
-
assert_match(/TestOverTTRJob.* hit 1s timeout/, out)
|
|
386
|
-
assert_match(/TestOverTTRJob was no longer reserved/, out)
|
|
387
|
-
# beanstalkd took the job back when its TTR ran out and hands it out again
|
|
388
|
-
pop_one_job("foo.bar.ttr") { |job, body| assert_equal "TestOverTTRJob", body["class"]; job.delete }
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
it "should keep working when the TTR ran out before the job could be retried" do
|
|
392
|
-
Backburner.configure { |config| config.max_job_retries = 1; config.retry_delay = 0 }
|
|
393
|
-
clear_jobs!("foo.bar.ttr")
|
|
394
|
-
@worker_class.enqueue TestOverTTRJob, [], :queue => "foo.bar.ttr"
|
|
395
|
-
worker = @worker_class.new('foo.bar.ttr')
|
|
396
|
-
out = silenced(6) do
|
|
397
|
-
worker.prepare
|
|
398
|
-
worker.work_one_job
|
|
399
|
-
end
|
|
400
|
-
assert_match(/TestOverTTRJob was no longer reserved/, out)
|
|
401
|
-
pop_one_job("foo.bar.ttr") { |job, body| assert_equal "TestOverTTRJob", body["class"]; job.delete }
|
|
402
|
-
end
|
|
403
|
-
|
|
404
|
-
it "should keep working when the connection is lost before the failed job could be retried or buried" do
|
|
405
|
-
clear_jobs!("foo.bar.lost")
|
|
406
|
-
@worker_class.enqueue TestFailJob, [1, 2], :queue => "foo.bar.lost"
|
|
407
|
-
worker = @worker_class.new('foo.bar.lost')
|
|
408
|
-
errors = []
|
|
409
|
-
Backburner.configure { |config| config.on_error = lambda { |e| errors << e } }
|
|
410
|
-
# beanstalkd went away while the job ran: asking for its stats (to retry or bury it)
|
|
411
|
-
# raises NotConnected
|
|
412
|
-
Beaneater::Job.any_instance.stubs(:ttr).returns(10)
|
|
413
|
-
Beaneater::Job.any_instance.stubs(:stats).raises(Beaneater::NotConnected)
|
|
414
|
-
out = silenced(2) do
|
|
415
|
-
worker.prepare
|
|
416
|
-
worker.work_one_job
|
|
417
|
-
end
|
|
418
|
-
assert_match(/TestFailJob was no longer reserved/, out)
|
|
419
|
-
assert_equal [RuntimeError], errors.map(&:class)
|
|
420
|
-
ensure
|
|
421
|
-
Beaneater::Job.any_instance.unstub(:stats)
|
|
422
|
-
Beaneater::Job.any_instance.unstub(:ttr)
|
|
423
|
-
Backburner.configure { |config| config.on_error = nil }
|
|
424
|
-
end
|
|
425
|
-
|
|
426
|
-
describe "with a job whose class can't be found" do
|
|
427
|
-
before do
|
|
428
|
-
clear_jobs!("foo.bar.unknown")
|
|
429
|
-
@errors = []
|
|
430
|
-
Backburner.configure { |config| config.on_error = lambda { |e| @errors << e } }
|
|
431
|
-
@worker = @worker_class.new('foo.bar.unknown')
|
|
432
|
-
end
|
|
433
|
-
|
|
434
|
-
after do
|
|
435
|
-
Backburner.configure { |config| config.on_error = nil }
|
|
436
|
-
clear_jobs!("foo.bar.unknown")
|
|
437
|
-
end
|
|
438
|
-
|
|
439
|
-
def put_job(class_name, ttr)
|
|
440
|
-
body = Backburner.configuration.job_serializer_proc.call(:class => class_name, :args => [1])
|
|
441
|
-
@worker.connection.tubes["demo.test.foo.bar.unknown"].put(body, :ttr => ttr)
|
|
442
|
-
end
|
|
443
|
-
|
|
444
|
-
def tube_stats
|
|
445
|
-
@worker.connection.tubes["demo.test.foo.bar.unknown"].stats
|
|
446
|
-
end
|
|
447
|
-
|
|
448
|
-
it "should release it for its TTR instead of burying it, and keep working" do
|
|
449
|
-
put_job("TestRemovedJob", 30)
|
|
450
|
-
out = silenced(2) do
|
|
451
|
-
@worker.prepare
|
|
452
|
-
@worker.work_one_job
|
|
453
|
-
end
|
|
454
|
-
assert_match(/Exception Backburner::Job::JobNotFound -> TestRemovedJob/, out)
|
|
455
|
-
assert_match(/TestRemovedJob: class not found; released for 30s/, out)
|
|
456
|
-
assert_equal [], @errors, "on_error isn't called for a class this worker lacks"
|
|
457
|
-
assert_equal 0, tube_stats.current_jobs_buried
|
|
458
|
-
assert_equal 1, tube_stats.current_jobs_delayed
|
|
459
|
-
assert_equal 30, @worker.connection.tubes["demo.test.foo.bar.unknown"].peek(:delayed).stats.delay
|
|
460
|
-
end
|
|
461
|
-
|
|
462
|
-
it "should leave it for a worker that has its class, however many retries it is allowed" do
|
|
463
|
-
Backburner.configure { |config| config.max_job_retries = 0 }
|
|
464
|
-
put_job("TestRolloutJob", 2)
|
|
465
|
-
out = silenced(8) do
|
|
466
|
-
@worker.prepare
|
|
467
|
-
@worker.work_one_job # this worker doesn't have the class yet
|
|
468
|
-
Object.const_set(:TestRolloutJob, Class.new { def self.perform(x); $worker_test_count += x; end })
|
|
469
|
-
@worker.work_one_job # now it does: the job comes back after its TTR
|
|
470
|
-
end
|
|
471
|
-
assert_match(/TestRolloutJob: class not found; released for 2s/, out)
|
|
472
|
-
assert_match(/Completed TestRolloutJob/, out)
|
|
473
|
-
assert_equal 1, $worker_test_count
|
|
474
|
-
assert_equal 0, tube_stats.current_jobs_buried
|
|
475
|
-
ensure
|
|
476
|
-
Object.send(:remove_const, :TestRolloutJob) if Object.const_defined?(:TestRolloutJob)
|
|
477
|
-
end
|
|
478
|
-
|
|
479
|
-
it "should release a job whose class an autoloader can't load" do
|
|
480
|
-
put_job("AutoloadingDemo::TestJobGone", 30)
|
|
481
|
-
out = silenced(2) do
|
|
482
|
-
@worker.prepare
|
|
483
|
-
@worker.work_one_job
|
|
484
|
-
end
|
|
485
|
-
assert_match(/AutoloadingDemo::TestJobGone: class not found; released for 30s/, out)
|
|
486
|
-
assert_equal [0, 1], [tube_stats.current_jobs_buried, tube_stats.current_jobs_delayed]
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
[["TestBrokenNameJob", "NameError"], ["TestBrokenRequireJob", "LoadError"], ["TestBrokenNoMethodJob", "NoMethodError"],
|
|
490
|
-
["TestBrokenRuntimeJob", "ArgumentError"], ["TestSameNameJob", "NameError"], ["TestBrokenSyntaxJob", "SyntaxError"],
|
|
491
|
-
["not a class", "NameError"], ["TestBrokenTimeoutJob", "Timeout::Error"]].each do |class_name, error|
|
|
492
|
-
it "should retry and bury a job whose class fails to load or can't exist (#{class_name}), calling on_error" do
|
|
493
|
-
Backburner.configure { |config| config.max_job_retries = 1; config.retry_delay = 0 }
|
|
494
|
-
put_job(class_name, 30)
|
|
495
|
-
out = silenced(4) do
|
|
496
|
-
@worker.prepare
|
|
497
|
-
2.times { @worker.work_one_job }
|
|
498
|
-
end
|
|
499
|
-
assert_match(/Exception Backburner::Job::JobNotFound -> #{class_name} \(#{error}: /, out)
|
|
500
|
-
assert_match(/attempt 1 of 2, retrying in 0s/, out)
|
|
501
|
-
assert_match(/attempt 2 of 2, burying/, out)
|
|
502
|
-
refute_match(/class not found/, out)
|
|
503
|
-
assert_equal [Backburner::Job::JobNotFound] * 2, @errors.map(&:class)
|
|
504
|
-
assert_equal 1, tube_stats.current_jobs_buried
|
|
505
|
-
end
|
|
506
|
-
end
|
|
507
|
-
|
|
508
|
-
%w(Backburner::TestGoneJob TestGoneNamespace::TestJob TestNoFileJob).each do |class_name|
|
|
509
|
-
it "should release a job whose class is missing (#{class_name})" do
|
|
510
|
-
put_job(class_name, 30)
|
|
511
|
-
out = silenced(2) do
|
|
512
|
-
@worker.prepare
|
|
513
|
-
@worker.work_one_job
|
|
514
|
-
end
|
|
515
|
-
assert_match(/#{class_name}: class not found; released for 30s/, out)
|
|
516
|
-
assert_equal [], @errors
|
|
517
|
-
assert_equal [0, 1], [tube_stats.current_jobs_buried, tube_stats.current_jobs_delayed]
|
|
518
|
-
end
|
|
519
|
-
end
|
|
520
|
-
|
|
521
|
-
it "should release it for at most MAX_CLASS_NOT_FOUND_DELAY seconds" do
|
|
522
|
-
put_job("TestRemovedJob", 3600)
|
|
523
|
-
out = silenced(2) do
|
|
524
|
-
@worker.prepare
|
|
525
|
-
@worker.work_one_job
|
|
526
|
-
end
|
|
527
|
-
assert_match(/TestRemovedJob: class not found; released for 60s/, out)
|
|
528
|
-
assert_equal 60, @worker.connection.tubes["demo.test.foo.bar.unknown"].peek(:delayed).stats.delay
|
|
529
|
-
end
|
|
530
|
-
|
|
531
|
-
it "should not call on_error when its TTR ran out before it could be released" do
|
|
532
|
-
put_job("TestRemovedJob", 1)
|
|
533
|
-
def @worker.reserve_job(*args); job = super; job.ttr; sleep 2.2; job; end # its TTR runs out first
|
|
534
|
-
out = silenced(6) do
|
|
535
|
-
@worker.prepare
|
|
536
|
-
@worker.work_one_job
|
|
537
|
-
end
|
|
538
|
-
assert_match(/TestRemovedJob was no longer reserved/, out)
|
|
539
|
-
assert_equal [], @errors
|
|
540
|
-
assert_equal [0, 1], [tube_stats.current_jobs_buried, tube_stats.current_jobs_ready]
|
|
541
|
-
end
|
|
542
|
-
end # unknown class
|
|
543
|
-
|
|
544
|
-
it "should keep working when the on_error handler raises" do
|
|
545
|
-
clear_jobs!("foo.bar.onerror")
|
|
546
|
-
Backburner.configure { |config| config.on_error = lambda { |e| raise SocketError, "error reporter unreachable" } }
|
|
547
|
-
@worker_class.enqueue TestFailJob, [1, 2], :queue => "foo.bar.onerror"
|
|
548
|
-
@worker_class.enqueue TestJob, [1, 2], :queue => "foo.bar.onerror"
|
|
549
|
-
worker = @worker_class.new('foo.bar.onerror')
|
|
550
|
-
out = silenced(4) do
|
|
551
|
-
worker.prepare
|
|
552
|
-
2.times { worker.work_one_job }
|
|
553
|
-
end
|
|
554
|
-
assert_match(/Finished TestFailJob.*attempt 1 of 1, burying/, out)
|
|
555
|
-
assert_match(/The on_error handler failed for TestFailJob: Exception SocketError -> error reporter unreachable/, out)
|
|
556
|
-
assert_equal 1, worker.connection.tubes["demo.test.foo.bar.onerror"].stats.current_jobs_buried
|
|
557
|
-
assert_equal 3, $worker_test_count
|
|
558
|
-
ensure
|
|
559
|
-
Backburner.configure { |config| config.on_error = nil }
|
|
560
|
-
clear_jobs!("foo.bar.onerror")
|
|
561
|
-
end
|
|
562
|
-
|
|
563
|
-
it "should use the connection given as an argument" do
|
|
564
|
-
worker = @worker_class.new('foo.bar')
|
|
565
|
-
connection = mock('connection')
|
|
566
|
-
worker.expects(:reserve_job).with(connection).returns(stub_everything('job'))
|
|
567
|
-
capture_stdout { worker.work_one_job(connection) }
|
|
568
|
-
end
|
|
569
|
-
|
|
570
|
-
after do
|
|
571
|
-
Backburner.configure do |config|
|
|
572
|
-
config.max_job_retries = 0
|
|
573
|
-
config.retry_delay = 5
|
|
574
|
-
config.retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) }
|
|
575
|
-
end
|
|
576
|
-
end
|
|
577
|
-
end # work_one_job
|
|
578
|
-
|
|
579
|
-
describe "when beanstalkd is down for longer than the reconnect window" do
|
|
580
|
-
before do
|
|
581
|
-
pick_beanstalkd_port
|
|
582
|
-
@templogger = Templogger.new('/tmp')
|
|
583
|
-
@beanstalk_url = Backburner.configuration.beanstalk_url
|
|
584
|
-
Backburner.configure do |config|
|
|
585
|
-
config.beanstalk_url = "beanstalk://127.0.0.1:#{@port}"
|
|
586
|
-
config.logger = @templogger.logger
|
|
587
|
-
end
|
|
588
|
-
start_beanstalkd
|
|
589
|
-
$worker_test_count = 0
|
|
590
|
-
end
|
|
591
|
-
|
|
592
|
-
after do
|
|
593
|
-
@worker_thread.kill if @worker_thread
|
|
594
|
-
stop_beanstalkd
|
|
595
|
-
if @templogger
|
|
596
|
-
Backburner.configure do |config|
|
|
597
|
-
config.beanstalk_url = @beanstalk_url
|
|
598
|
-
config.logger = nil
|
|
599
|
-
end
|
|
600
|
-
@templogger.close
|
|
601
|
-
end
|
|
602
|
-
end
|
|
603
|
-
|
|
604
|
-
it "should reconnect, watch its tubes again and resume working" do
|
|
605
|
-
worker = @worker_class.new('foo.bar.outage')
|
|
606
|
-
worker.prepare
|
|
607
|
-
@worker_thread = Thread.new { loop { worker.work_one_job } }
|
|
608
|
-
sleep 0.5 # let the worker block in reserve
|
|
609
|
-
|
|
610
|
-
stop_beanstalkd
|
|
611
|
-
# beaneater's own reconnect attempts fail, then Connection#ensure_connected! gives up too
|
|
612
|
-
wait_for { @templogger.body.scan(/Exception Beaneater::NotConnected/).size >= 2 }
|
|
613
|
-
start_beanstalkd
|
|
614
|
-
@worker_class.enqueue TestJob, [1, 2], :queue => 'foo.bar.outage'
|
|
615
|
-
wait_for(10) { $worker_test_count == 3 }
|
|
616
|
-
|
|
617
|
-
errors = @templogger.body.scan(/ERROR -- /).size
|
|
618
|
-
assert_operator errors, :<, 20, "the worker logged #{errors} errors (busy loop?)"
|
|
619
|
-
assert_equal 3, $worker_test_count
|
|
620
|
-
end
|
|
621
|
-
end # beanstalkd outage
|
|
622
|
-
end # Worker
|