backburner 1.6.1 → 1.8.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/.github/workflows/ci.yml +45 -0
- data/.github/workflows/github-release.yml +58 -0
- data/.github/workflows/release.yml +221 -0
- data/CHANGELOG.md +33 -0
- data/Gemfile +4 -1
- data/HOOKS.md +23 -14
- data/README.md +32 -9
- data/backburner.gemspec +6 -1
- data/bin/backburner +1 -1
- data/lib/active_job/queue_adapters/backburner_adapter.rb +41 -0
- data/lib/backburner/configuration.rb +7 -0
- data/lib/backburner/connection.rb +4 -2
- data/lib/backburner/enqueue_pool.rb +88 -0
- data/lib/backburner/helpers.rb +77 -22
- data/lib/backburner/job.rb +46 -7
- data/lib/backburner/logger.rb +9 -0
- data/lib/backburner/performable.rb +1 -1
- data/lib/backburner/queue.rb +37 -19
- data/lib/backburner/version.rb +1 -1
- data/lib/backburner/worker.rb +112 -21
- data/lib/backburner/workers/forking.rb +4 -2
- data/lib/backburner/workers/simple.rb +5 -0
- data/lib/backburner/workers/threading.rb +1 -7
- data/lib/backburner/workers/threads_on_fork.rb +58 -23
- data/lib/backburner.rb +7 -0
- data/test/active_job_adapter_test.rb +24 -0
- data/test/back_burner_test.rb +13 -0
- data/test/connection_test.rb +22 -0
- data/test/fixtures/active_job_app.rb +53 -0
- data/test/fixtures/active_jobs.rb +11 -0
- data/test/fixtures/autoload/test_broken_name_job.rb +4 -0
- data/test/fixtures/autoload/test_broken_no_method_job.rb +4 -0
- data/test/fixtures/autoload/test_broken_require_job.rb +4 -0
- data/test/fixtures/autoload/test_broken_runtime_job.rb +4 -0
- data/test/fixtures/autoload/test_broken_syntax_job.rb +4 -0
- data/test/fixtures/autoload/test_broken_timeout_job.rb +4 -0
- data/test/fixtures/autoload/test_same_name_job.rb +5 -0
- data/test/fixtures/test_fork_jobs.rb +5 -0
- data/test/fixtures/test_jobs.rb +53 -0
- data/test/fixtures/test_queue_settings.rb +41 -1
- data/test/helpers/templogger.rb +1 -1
- data/test/helpers_test.rb +184 -0
- data/test/job_test.rb +22 -0
- data/test/logger_test.rb +27 -0
- data/test/performable_test.rb +50 -0
- data/test/queue_test.rb +63 -0
- data/test/test_helper.rb +59 -5
- data/test/worker_test.rb +346 -1
- data/test/workers/forking_worker_test.rb +19 -1
- data/test/workers/simple_worker_test.rb +272 -0
- data/test/workers/threading_worker_test.rb +22 -1
- data/test/workers/threads_on_fork_worker_test.rb +279 -12
- metadata +87 -8
- data/.travis.yml +0 -29
- data/circle.yml +0 -3
|
@@ -256,6 +256,35 @@ describe "Backburner::Workers::Simple module" do
|
|
|
256
256
|
assert_equal true, $worker_success
|
|
257
257
|
end
|
|
258
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
|
+
|
|
259
288
|
it "should support event hooks without retry" do
|
|
260
289
|
$hooked_fail_count = 0
|
|
261
290
|
clear_jobs!('foo.bar.events')
|
|
@@ -332,6 +361,205 @@ describe "Backburner::Workers::Simple module" do
|
|
|
332
361
|
refute_match(/HookFailError/, out)
|
|
333
362
|
end # stopping perform
|
|
334
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
|
+
|
|
335
563
|
it "should use the connection given as an argument" do
|
|
336
564
|
worker = @worker_class.new('foo.bar')
|
|
337
565
|
connection = mock('connection')
|
|
@@ -347,4 +575,48 @@ describe "Backburner::Workers::Simple module" do
|
|
|
347
575
|
end
|
|
348
576
|
end
|
|
349
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
|
|
350
622
|
end # Worker
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require File.expand_path('../../test_helper', __FILE__)
|
|
2
2
|
require File.expand_path('../../fixtures/test_jobs', __FILE__)
|
|
3
3
|
require File.expand_path('../../fixtures/hooked', __FILE__)
|
|
4
|
+
require File.expand_path('../../fixtures/test_queue_settings', __FILE__)
|
|
4
5
|
|
|
5
6
|
describe "Backburner::Workers::Threading worker" do
|
|
6
7
|
before do
|
|
@@ -51,6 +52,21 @@ describe "Backburner::Workers::Threading worker" do
|
|
|
51
52
|
end
|
|
52
53
|
end # process_tube_names
|
|
53
54
|
|
|
55
|
+
describe "for process_tube_options method" do
|
|
56
|
+
it "should take a shared tube's limit from the class that owns the tube, not from subclasses" do
|
|
57
|
+
worker = @worker_class.new(%w(tube-limits tube-limits-testtubelimitslambdachildjob))
|
|
58
|
+
assert_equal({
|
|
59
|
+
"demo.test.tube-limits" => { :threads => 5 },
|
|
60
|
+
"demo.test.tube-limits-testtubelimitslambdachildjob" => { :threads => 4 }
|
|
61
|
+
}, worker.instance_variable_get("@tubes_data"))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "should fill in a shared tube's limit from a subclass when the tube's class sets none" do
|
|
65
|
+
worker = @worker_class.new(%w(tube-few-limits))
|
|
66
|
+
assert_equal({ "demo.test.tube-few-limits" => { :threads => 3 } }, worker.instance_variable_get("@tubes_data"))
|
|
67
|
+
end
|
|
68
|
+
end # process_tube_options
|
|
69
|
+
|
|
54
70
|
describe 'working a queue' do
|
|
55
71
|
before do
|
|
56
72
|
@worker = @worker_class.new(["foo:3"])
|
|
@@ -91,8 +107,13 @@ describe "Backburner::Workers::Threading worker" do
|
|
|
91
107
|
assert_equal @thread_count, $worker_test_count
|
|
92
108
|
end
|
|
93
109
|
|
|
110
|
+
# Its own tube and worker: the jobs it leaves reserved go back to ready when their connections are
|
|
111
|
+
# finalized, and must not reach another test's workers
|
|
94
112
|
it 'forces an exit when a job is stuck' do
|
|
95
|
-
|
|
113
|
+
clear_jobs!("threaded-stuck")
|
|
114
|
+
@worker = @worker_class.new(["threaded-stuck:#{@thread_count}"])
|
|
115
|
+
@worker.exit_on_shutdown = false
|
|
116
|
+
6.times { @worker_class.enqueue TestStuckJob, [1, 0], :queue => "threaded-stuck" }
|
|
96
117
|
Thread.new { sleep 0.1; @worker.self_write.puts("TERM") }
|
|
97
118
|
capture_stdout do
|
|
98
119
|
@worker.start
|