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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -4
  3. data/README.md +70 -14
  4. data/lib/active_job/queue_adapters/backburner_adapter.rb +9 -2
  5. data/lib/backburner/cli.rb +1 -1
  6. data/lib/backburner/enqueue_pool.rb +2 -0
  7. data/lib/backburner/performable.rb +28 -3
  8. data/lib/backburner/version.rb +1 -1
  9. data/lib/backburner/worker.rb +38 -4
  10. data/lib/backburner/workers/threading.rb +16 -0
  11. data/lib/backburner/workers/threads_on_fork.rb +1 -0
  12. metadata +8 -82
  13. data/.github/workflows/ci.yml +0 -45
  14. data/.github/workflows/github-release.yml +0 -58
  15. data/.github/workflows/release.yml +0 -221
  16. data/.gitignore +0 -17
  17. data/CONTRIBUTING.md +0 -37
  18. data/Gemfile +0 -7
  19. data/Rakefile +0 -17
  20. data/TODO +0 -4
  21. data/backburner.gemspec +0 -31
  22. data/examples/custom.rb +0 -25
  23. data/examples/demo.rb +0 -60
  24. data/examples/god.rb +0 -46
  25. data/examples/hooked.rb +0 -87
  26. data/examples/retried.rb +0 -31
  27. data/examples/simple.rb +0 -43
  28. data/examples/stress.rb +0 -31
  29. data/test/active_job_adapter_test.rb +0 -24
  30. data/test/async_proxy_test.rb +0 -36
  31. data/test/back_burner_test.rb +0 -101
  32. data/test/connection_test.rb +0 -201
  33. data/test/fixtures/active_job_app.rb +0 -53
  34. data/test/fixtures/active_jobs.rb +0 -11
  35. data/test/fixtures/autoload/test_broken_name_job.rb +0 -4
  36. data/test/fixtures/autoload/test_broken_no_method_job.rb +0 -4
  37. data/test/fixtures/autoload/test_broken_require_job.rb +0 -4
  38. data/test/fixtures/autoload/test_broken_runtime_job.rb +0 -4
  39. data/test/fixtures/autoload/test_broken_syntax_job.rb +0 -4
  40. data/test/fixtures/autoload/test_broken_timeout_job.rb +0 -4
  41. data/test/fixtures/autoload/test_same_name_job.rb +0 -5
  42. data/test/fixtures/hooked.rb +0 -122
  43. data/test/fixtures/test_fork_jobs.rb +0 -77
  44. data/test/fixtures/test_forking_jobs.rb +0 -56
  45. data/test/fixtures/test_jobs.rb +0 -140
  46. data/test/fixtures/test_queue_settings.rb +0 -54
  47. data/test/helpers/templogger.rb +0 -22
  48. data/test/helpers_test.rb +0 -462
  49. data/test/hooks_test.rb +0 -112
  50. data/test/job_test.rb +0 -207
  51. data/test/logger_test.rb +0 -71
  52. data/test/performable_test.rb +0 -138
  53. data/test/queue_test.rb +0 -132
  54. data/test/test_helper.rb +0 -182
  55. data/test/worker_test.rb +0 -502
  56. data/test/workers/forking_worker_test.rb +0 -199
  57. data/test/workers/simple_worker_test.rb +0 -622
  58. data/test/workers/threading_worker_test.rb +0 -125
  59. 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