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
data/test/test_helper.rb
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
require 'rubygems'
|
|
2
|
-
require 'tempfile'
|
|
3
|
-
require 'minitest/autorun'
|
|
4
|
-
require 'mocha/minitest'
|
|
5
|
-
|
|
6
|
-
$LOAD_PATH.unshift File.expand_path("lib")
|
|
7
|
-
require 'backburner'
|
|
8
|
-
require File.expand_path('../helpers/templogger', __FILE__)
|
|
9
|
-
|
|
10
|
-
# Configure Backburner
|
|
11
|
-
Backburner.configure do |config|
|
|
12
|
-
config.beanstalk_url = "beanstalk://127.0.0.1"
|
|
13
|
-
config.tube_namespace = "demo.test"
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
## Kernel Extensions
|
|
17
|
-
require 'stringio'
|
|
18
|
-
|
|
19
|
-
module Kernel
|
|
20
|
-
# Redirect standard out, standard error and the buffered logger for sprinkle to StringIO
|
|
21
|
-
# capture_stdout { any_commands; you_want } => "all output from the commands"
|
|
22
|
-
def capture_stdout
|
|
23
|
-
if ENV['DEBUG'] # Skip if debug mode
|
|
24
|
-
yield
|
|
25
|
-
return ""
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
out = StringIO.new
|
|
29
|
-
$stdout = out
|
|
30
|
-
$stderr = out
|
|
31
|
-
yield
|
|
32
|
-
return out.string
|
|
33
|
-
ensure
|
|
34
|
-
$stdout = STDOUT
|
|
35
|
-
$stderr = STDERR
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
class User
|
|
40
|
-
attr_accessor :id, :name
|
|
41
|
-
|
|
42
|
-
def initialize(id, name)
|
|
43
|
-
@id, @name = id, name
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
class MiniTest::Spec
|
|
48
|
-
class << self
|
|
49
|
-
alias :should :it
|
|
50
|
-
alias :context :describe
|
|
51
|
-
end
|
|
52
|
-
alias :assert_no_match :refute_match
|
|
53
|
-
alias :assert_not_nil :refute_nil
|
|
54
|
-
alias :assert_not_equal :refute_equal
|
|
55
|
-
|
|
56
|
-
# assert_same_elements([:a, :b, :c], [:c, :a, :b]) => passes
|
|
57
|
-
def assert_same_elements(a1, a2, msg = nil)
|
|
58
|
-
[:select, :inject, :size].each do |m|
|
|
59
|
-
[a1, a2].each {|a| assert_respond_to(a, m, "Are you sure that #{a.inspect} is an array? It doesn't respond to #{m}.") }
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
assert a1h = a1.inject({}) { |h,e| h[e] ||= a1.select { |i| i == e }.size; h }
|
|
63
|
-
assert a2h = a2.inject({}) { |h,e| h[e] ||= a2.select { |i| i == e }.size; h }
|
|
64
|
-
|
|
65
|
-
assert_equal(a1h, a2h, msg)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# assert_contains(['a', '1'], /\d/) => passes
|
|
69
|
-
# assert_contains(['a', '1'], 'a') => passes
|
|
70
|
-
# assert_contains(['a', '1'], /not there/) => fails
|
|
71
|
-
def assert_contains(collection, x, extra_msg = "")
|
|
72
|
-
collection = [collection] unless collection.is_a?(Array)
|
|
73
|
-
msg = "#{x.inspect} not found in #{collection.to_a.inspect} #{extra_msg}"
|
|
74
|
-
case x
|
|
75
|
-
when Regexp
|
|
76
|
-
assert(collection.detect { |e| e =~ x }, msg)
|
|
77
|
-
else
|
|
78
|
-
assert(collection.include?(x), msg)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# silenced(5) { ... }
|
|
83
|
-
def silenced(time=3, &block)
|
|
84
|
-
Timeout::timeout(time) { capture_stdout(&block) }
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def beanstalk_connection
|
|
88
|
-
Backburner::Connection.new(Backburner.configuration.beanstalk_url)
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# pop_one_job(tube_name)
|
|
92
|
-
def pop_one_job(tube_name=Backburner.configuration.primary_queue)
|
|
93
|
-
tube_name = [Backburner.configuration.tube_namespace, tube_name].join(".")
|
|
94
|
-
connection = beanstalk_connection
|
|
95
|
-
connection.tubes.watch!(tube_name)
|
|
96
|
-
silenced(3) { @res = connection.tubes.reserve }
|
|
97
|
-
yield @res, Backburner.configuration.job_parser_proc.call(@res.body)
|
|
98
|
-
ensure
|
|
99
|
-
connection.close if connection
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# clear_jobs!('foo')
|
|
103
|
-
def clear_jobs!(*tube_names)
|
|
104
|
-
connection = beanstalk_connection
|
|
105
|
-
tube_names.each do |tube_name|
|
|
106
|
-
expanded_name = [Backburner.configuration.tube_namespace, tube_name].join(".")
|
|
107
|
-
connection.tubes.find(expanded_name).clear
|
|
108
|
-
end
|
|
109
|
-
ensure
|
|
110
|
-
connection.close if connection
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# A beanstalkd of the test's own (on @port) that it can stop and start, for outage tests:
|
|
114
|
-
# pick_beanstalkd_port in `before`, then start_beanstalkd, and stop_beanstalkd in `after`
|
|
115
|
-
def pick_beanstalkd_port
|
|
116
|
-
@beanstalkd_bin = ENV['BEANSTALKD_BIN'] || ENV['PATH'].split(File::PATH_SEPARATOR).
|
|
117
|
-
map { |dir| File.join(dir, 'beanstalkd') }.find { |file| File.executable?(file) }
|
|
118
|
-
skip "needs a beanstalkd binary on PATH or in BEANSTALKD_BIN" unless @beanstalkd_bin
|
|
119
|
-
server = TCPServer.new('127.0.0.1', 0)
|
|
120
|
-
@port = server.addr[1]
|
|
121
|
-
server.close
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def start_beanstalkd
|
|
125
|
-
@beanstalkd_pid = Process.spawn(@beanstalkd_bin, '-l', '127.0.0.1', '-p', @port.to_s, [:out, :err] => File::NULL)
|
|
126
|
-
wait_for { (TCPSocket.new('127.0.0.1', @port).close; true) rescue false }
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
def stop_beanstalkd
|
|
130
|
-
return unless @beanstalkd_pid
|
|
131
|
-
Process.kill(:KILL, @beanstalkd_pid)
|
|
132
|
-
Process.wait(@beanstalkd_pid)
|
|
133
|
-
@beanstalkd_pid = nil
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def wait_for(seconds = 15)
|
|
137
|
-
deadline = Time.now + seconds
|
|
138
|
-
sleep 0.1 until yield || Time.now > deadline
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# Simulates a broken connection for any Beaneater::Connection. Will
|
|
142
|
-
# simulate a restored connection after `reconnects_after`. This is expected
|
|
143
|
-
# to be used when ensuring a Beaneater connection is open, therefore
|
|
144
|
-
def simulate_disconnect(connection, reconnects_after = 2)
|
|
145
|
-
connection.beanstalk.connection.connection.expects(:closed? => true)
|
|
146
|
-
returns = Array.new(reconnects_after - 1, stub('TCPSocket'))
|
|
147
|
-
returns.each do |socket|
|
|
148
|
-
result = (socket != returns.last)
|
|
149
|
-
socket.stubs(:closed? => result)
|
|
150
|
-
end
|
|
151
|
-
TCPSocket.expects(:new).times(returns.size).returns(*returns)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# Whether a forked child's GC was disabled by the time the interpreter tore it down (when its
|
|
155
|
-
# at_exit handlers run). Yields the child's body, which ends as `how` says (:return, :raise, or
|
|
156
|
-
# :shutdown, which calls Kernel.exit as the TERM and INT traps do); the block forks a child that
|
|
157
|
-
# runs it and waits for the child. Fails if the GC is already off here (the child would inherit
|
|
158
|
-
# that), or if the child never ran its at_exit handlers (it left by exit!, or crashed).
|
|
159
|
-
def gc_disabled_at_child_exit?(worker, how)
|
|
160
|
-
gc_was_disabled = GC.enable # returns whether it was disabled
|
|
161
|
-
GC.disable if gc_was_disabled
|
|
162
|
-
refute gc_was_disabled, "the GC was already disabled in the test process"
|
|
163
|
-
reader, writer = IO.pipe
|
|
164
|
-
yield(lambda do
|
|
165
|
-
reader.close
|
|
166
|
-
$stdout.reopen(File::NULL)
|
|
167
|
-
$stderr.reopen(File::NULL)
|
|
168
|
-
at_exit { writer.write(GC.disable ? "disabled" : "enabled"); writer.close }
|
|
169
|
-
case how
|
|
170
|
-
when :raise then raise "the child failed"
|
|
171
|
-
when :shutdown then worker.shutdown
|
|
172
|
-
end
|
|
173
|
-
end)
|
|
174
|
-
writer.close
|
|
175
|
-
state = reader.read
|
|
176
|
-
flunk "the child never ran its at_exit handlers (it left by exit!, or crashed)" if state.empty?
|
|
177
|
-
state == "disabled"
|
|
178
|
-
ensure
|
|
179
|
-
reader.close if reader && !reader.closed?
|
|
180
|
-
writer.close if writer && !writer.closed?
|
|
181
|
-
end
|
|
182
|
-
end # MiniTest::Spec
|
data/test/worker_test.rb
DELETED
|
@@ -1,502 +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::Worker module" do
|
|
6
|
-
before do
|
|
7
|
-
Backburner.default_queues.clear
|
|
8
|
-
clear_jobs!(Backburner.configuration.primary_queue, "test-plain", "test.bar", "bar.baz.foo", "test-parent")
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
describe "for enqueue class method" do
|
|
12
|
-
it "should support enqueuing plain job" do
|
|
13
|
-
Backburner::Worker.enqueue TestPlainJob, [7, 9], :ttr => 100, :pri => 2000
|
|
14
|
-
pop_one_job("test-plain") do |job, body|
|
|
15
|
-
assert_equal "TestPlainJob", body["class"]
|
|
16
|
-
assert_equal [7, 9], body["args"]
|
|
17
|
-
assert_equal 100, job.ttr
|
|
18
|
-
assert_equal 2000, job.pri
|
|
19
|
-
end
|
|
20
|
-
end # plain
|
|
21
|
-
|
|
22
|
-
it "should support enqueuing job with class queue priority" do
|
|
23
|
-
Backburner::Worker.enqueue TestJob, [3, 4], :ttr => 100
|
|
24
|
-
pop_one_job do |job, body|
|
|
25
|
-
assert_equal "TestJob", body["class"]
|
|
26
|
-
assert_equal [3, 4], body["args"]
|
|
27
|
-
assert_equal 100, job.ttr
|
|
28
|
-
assert_equal 100, job.pri
|
|
29
|
-
end
|
|
30
|
-
end # queue priority
|
|
31
|
-
|
|
32
|
-
it "should support enqueuing job with specified named priority" do
|
|
33
|
-
Backburner::Worker.enqueue TestJob, [3, 4], :ttr => 100, :pri => 'high'
|
|
34
|
-
pop_one_job do |job, body|
|
|
35
|
-
assert_equal "TestJob", body["class"]
|
|
36
|
-
assert_equal [3, 4], body["args"]
|
|
37
|
-
assert_equal 100, job.ttr
|
|
38
|
-
assert_equal 0, job.pri
|
|
39
|
-
end
|
|
40
|
-
end # queue named priority
|
|
41
|
-
|
|
42
|
-
it "should support enqueuing job with class queue respond_timeout" do
|
|
43
|
-
Backburner::Worker.enqueue TestJob, [3, 4]
|
|
44
|
-
pop_one_job do |job, body|
|
|
45
|
-
assert_equal "TestJob", body["class"]
|
|
46
|
-
assert_equal [3, 4], body["args"]
|
|
47
|
-
assert_equal 300, job.ttr
|
|
48
|
-
assert_equal 100, job.pri
|
|
49
|
-
end
|
|
50
|
-
end # queue respond_timeout
|
|
51
|
-
|
|
52
|
-
it "should enqueue a subclass job on its superclass queue with its settings" do
|
|
53
|
-
Backburner::Worker.enqueue TestChildQueueJob, [3, 4]
|
|
54
|
-
pop_one_job("test-parent") do |job, body|
|
|
55
|
-
assert_equal "TestChildQueueJob", body["class"]
|
|
56
|
-
assert_equal [3, 4], body["args"]
|
|
57
|
-
assert_equal 250, job.ttr
|
|
58
|
-
assert_equal 0, job.pri
|
|
59
|
-
end
|
|
60
|
-
end # subclass queue
|
|
61
|
-
|
|
62
|
-
it "should support a priority label as the default priority" do
|
|
63
|
-
default_priority = Backburner.configuration.default_priority
|
|
64
|
-
Backburner.configuration.default_priority = :low
|
|
65
|
-
Backburner::Worker.enqueue TestPlainJob, [1, 2]
|
|
66
|
-
pop_one_job("test-plain") do |job, body|
|
|
67
|
-
assert_equal "TestPlainJob", body["class"]
|
|
68
|
-
assert_equal 200, job.pri
|
|
69
|
-
end
|
|
70
|
-
ensure
|
|
71
|
-
Backburner.configuration.default_priority = default_priority
|
|
72
|
-
end # default priority label
|
|
73
|
-
|
|
74
|
-
it "should support enqueuing job with custom queue" do
|
|
75
|
-
Backburner::Worker.enqueue TestJob, [6, 7], :queue => "test.bar", :pri => 5000
|
|
76
|
-
pop_one_job("test.bar") do |job, body|
|
|
77
|
-
assert_equal "TestJob", body["class"]
|
|
78
|
-
assert_equal [6, 7], body["args"]
|
|
79
|
-
assert_equal 0, job.delay
|
|
80
|
-
assert_equal 5000, job.pri
|
|
81
|
-
assert_equal 300, job.ttr
|
|
82
|
-
end
|
|
83
|
-
end # custom
|
|
84
|
-
|
|
85
|
-
it "should round a fractional delay up to whole seconds, so the job never runs early" do
|
|
86
|
-
conn = beanstalk_connection
|
|
87
|
-
{ nil => 0, -1 => 0, 0 => 0, 0.2 => 1, 1 => 1, 1.5 => 2, 2 => 2 }.each do |delay, expected|
|
|
88
|
-
response = Backburner::Worker.enqueue TestPlainJob, [1, 2], :delay => delay
|
|
89
|
-
assert_equal expected, conn.jobs.find(response[:id]).stats.delay, "delay #{delay.inspect}"
|
|
90
|
-
end
|
|
91
|
-
ensure
|
|
92
|
-
conn.close if conn
|
|
93
|
-
clear_jobs!("test-plain")
|
|
94
|
-
end # fractional delay
|
|
95
|
-
|
|
96
|
-
it "should round a fractional TTR up to whole seconds, from an option, a class or the default" do
|
|
97
|
-
respond_timeout = Backburner.configuration.respond_timeout
|
|
98
|
-
conn = beanstalk_connection
|
|
99
|
-
ttr_of = lambda { |response| conn.jobs.find(response[:id]).stats.ttr }
|
|
100
|
-
assert_equal 1, ttr_of.call(Backburner::Worker.enqueue(TestPlainJob, [1, 2], :ttr => 0.2))
|
|
101
|
-
assert_equal 2, ttr_of.call(Backburner::Worker.enqueue(TestFloatRespondTimeoutJob, []))
|
|
102
|
-
Backburner.configuration.respond_timeout = 2.5
|
|
103
|
-
assert_equal 3, ttr_of.call(Backburner::Worker.enqueue(TestPlainJob, [1, 2]))
|
|
104
|
-
ensure
|
|
105
|
-
Backburner.configuration.respond_timeout = respond_timeout
|
|
106
|
-
conn.close if conn
|
|
107
|
-
clear_jobs!("test-plain")
|
|
108
|
-
end # fractional ttr
|
|
109
|
-
|
|
110
|
-
it "should support async job" do
|
|
111
|
-
TestAsyncJob.async(:ttr => 100, :queue => "bar.baz.foo").foo(10, 5)
|
|
112
|
-
pop_one_job("bar.baz.foo") do |job, body|
|
|
113
|
-
assert_equal "TestAsyncJob", body["class"]
|
|
114
|
-
assert_equal [nil, "foo", 10, 5], body["args"]
|
|
115
|
-
assert_equal 100, job.ttr
|
|
116
|
-
assert_equal Backburner.configuration.default_priority, job.pri
|
|
117
|
-
end
|
|
118
|
-
end # async
|
|
119
|
-
|
|
120
|
-
it "should support enqueueing job with lambda queue" do
|
|
121
|
-
expected_queue_name = TestLambdaQueueJob.calculated_queue_name
|
|
122
|
-
Backburner::Worker.enqueue TestLambdaQueueJob, [6, 7], :queue => lambda { |klass| klass.calculated_queue_name }
|
|
123
|
-
pop_one_job(expected_queue_name) do |job, body|
|
|
124
|
-
assert_equal "TestLambdaQueueJob", body["class"]
|
|
125
|
-
assert_equal [6, 7], body["args"]
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end # enqueue
|
|
129
|
-
|
|
130
|
-
describe "for the enqueue connections" do
|
|
131
|
-
before do
|
|
132
|
-
GC.start # closes the connections earlier tests left to the garbage collector before a test counts them
|
|
133
|
-
@gc_was_disabled = GC.disable # and keeps a collection from closing one while it counts
|
|
134
|
-
@stats_connection = beanstalk_connection
|
|
135
|
-
@beanstalk_url = Backburner.configuration.beanstalk_url
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
after do
|
|
139
|
-
GC.enable unless @gc_was_disabled
|
|
140
|
-
@stats_connection.close
|
|
141
|
-
Backburner.configuration.beanstalk_url = @beanstalk_url
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# Connections beanstalkd has accepted so far
|
|
145
|
-
def total_connections
|
|
146
|
-
@stats_connection.stats.total_connections
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
def open_connections
|
|
150
|
-
@stats_connection.stats.current_connections
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def enqueue(n = 0)
|
|
154
|
-
Backburner::Worker.enqueue(TestJob, [n, 0])
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# Starts the pool afresh: a new URL (spelling the same beanstalkd) closes its idle connections.
|
|
158
|
-
# Leaves one idle connection in it.
|
|
159
|
-
def fresh_pool
|
|
160
|
-
["beanstalk://127.0.0.1:11300", "beanstalk://127.0.0.1:11300/"].each do |url|
|
|
161
|
-
Backburner.configuration.beanstalk_url = url
|
|
162
|
-
enqueue
|
|
163
|
-
end
|
|
164
|
-
settle
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# Waits for beanstalkd to have seen the connections closed so far
|
|
168
|
-
def settle
|
|
169
|
-
last = nil
|
|
170
|
-
wait_for(5) { (now = open_connections) == last || (last = now; sleep 0.1; false) }
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
it "should reuse a connection for sequential enqueues" do
|
|
174
|
-
enqueue
|
|
175
|
-
before_count = total_connections
|
|
176
|
-
responses = Array.new(5) { |i| enqueue(i) }
|
|
177
|
-
assert_equal ["INSERTED"] * 5, responses.map { |res| res[:status] }
|
|
178
|
-
assert_equal 0, total_connections - before_count
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
it "should reuse a connection across fibers and threads" do
|
|
182
|
-
enqueue
|
|
183
|
-
before_count = total_connections
|
|
184
|
-
5.times { |i| Fiber.new { enqueue(i) }.resume }
|
|
185
|
-
5.times { |i| Thread.new { enqueue(i) }.join }
|
|
186
|
-
assert_equal 0, total_connections - before_count
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
it "should keep at most 5 idle connections after concurrent enqueues" do
|
|
190
|
-
fresh_pool # one idle connection
|
|
191
|
-
before_total, before_open = total_connections, open_connections
|
|
192
|
-
threads, inside = 8, Queue.new
|
|
193
|
-
release = Queue.new
|
|
194
|
-
serializer = Backburner.configuration.job_serializer_proc
|
|
195
|
-
# every thread holds a connection until all of them do
|
|
196
|
-
Backburner.configuration.job_serializer_proc = lambda { |body| inside << 1; release.pop; body.to_json }
|
|
197
|
-
workers = Array.new(threads) { |i| Thread.new { enqueue(i) } }
|
|
198
|
-
threads.times { inside.pop }
|
|
199
|
-
threads.times { release << 1 }
|
|
200
|
-
workers.each(&:join)
|
|
201
|
-
Backburner.configuration.job_serializer_proc = serializer
|
|
202
|
-
assert_equal threads - 1, total_connections - before_total, "one reused, the rest opened"
|
|
203
|
-
kept = 5
|
|
204
|
-
wait_for(5) { open_connections - before_open == kept - 1 }
|
|
205
|
-
assert_equal kept - 1, open_connections - before_open, "only #{kept} stay open"
|
|
206
|
-
ensure
|
|
207
|
-
Backburner.configuration.job_serializer_proc = serializer if serializer
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
it "should give a forked child its own connection" do
|
|
211
|
-
skip "needs fork" unless Process.respond_to?(:fork)
|
|
212
|
-
enqueue(1)
|
|
213
|
-
before_child = total_connections
|
|
214
|
-
pid = Process.fork do
|
|
215
|
-
res = Backburner::Worker.enqueue(TestJob, [2, 0]) rescue nil
|
|
216
|
-
exit!(res && res[:status] == "INSERTED" ? 0 : 1)
|
|
217
|
-
end
|
|
218
|
-
Process.wait(pid)
|
|
219
|
-
assert_equal 0, $?.exitstatus, "the child's enqueue failed"
|
|
220
|
-
assert_equal 1, total_connections - before_child, "the child should open its own connection"
|
|
221
|
-
assert_equal "INSERTED", enqueue(3)[:status]
|
|
222
|
-
assert_equal 1, total_connections - before_child, "the parent should keep its connection"
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
it "should close a connection that an error interrupted instead of reusing it" do
|
|
226
|
-
fresh_pool # one idle connection
|
|
227
|
-
before_total, before_open = total_connections, open_connections
|
|
228
|
-
serializer = Backburner.configuration.job_serializer_proc
|
|
229
|
-
Backburner.configuration.job_serializer_proc = lambda { |body| raise Timeout::Error }
|
|
230
|
-
assert_raises(Timeout::Error) { enqueue(2) }
|
|
231
|
-
Backburner.configuration.job_serializer_proc = serializer
|
|
232
|
-
wait_for(5) { open_connections == before_open - 1 }
|
|
233
|
-
assert_equal before_open - 1, open_connections, "the interrupted connection should be closed"
|
|
234
|
-
assert_equal "INSERTED", enqueue(3)[:status]
|
|
235
|
-
assert_equal 1, total_connections - before_total, "the next enqueue should open a new connection"
|
|
236
|
-
ensure
|
|
237
|
-
Backburner.configuration.job_serializer_proc = serializer if serializer
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
it "should close a connection whose enqueue was killed" do
|
|
241
|
-
fresh_pool # one idle connection
|
|
242
|
-
before_total, before_open = total_connections, open_connections
|
|
243
|
-
serializer = Backburner.configuration.job_serializer_proc
|
|
244
|
-
inside = Queue.new
|
|
245
|
-
Backburner.configuration.job_serializer_proc = lambda { |body| inside << 1; sleep }
|
|
246
|
-
thread = Thread.new { enqueue(2) }
|
|
247
|
-
inside.pop
|
|
248
|
-
thread.kill.join
|
|
249
|
-
Backburner.configuration.job_serializer_proc = serializer
|
|
250
|
-
wait_for(5) { open_connections == before_open - 1 }
|
|
251
|
-
assert_equal before_open - 1, open_connections, "the killed enqueue's connection should be closed"
|
|
252
|
-
assert_equal "INSERTED", enqueue(3)[:status]
|
|
253
|
-
assert_equal 1, total_connections - before_total, "the next enqueue should open a new connection"
|
|
254
|
-
ensure
|
|
255
|
-
Backburner.configuration.job_serializer_proc = serializer if serializer
|
|
256
|
-
end
|
|
257
|
-
|
|
258
|
-
it "should replace a connection idle for longer than enqueue_connection_max_idle" do
|
|
259
|
-
max_idle = Backburner.configuration.enqueue_connection_max_idle
|
|
260
|
-
fresh_pool # one idle connection
|
|
261
|
-
before_total, before_open = total_connections, open_connections
|
|
262
|
-
Backburner.configuration.enqueue_connection_max_idle = 0.05
|
|
263
|
-
sleep 0.1
|
|
264
|
-
assert_equal "INSERTED", enqueue(1)[:status]
|
|
265
|
-
assert_equal 1, total_connections - before_total, "the expired connection should be replaced"
|
|
266
|
-
wait_for(5) { open_connections == before_open }
|
|
267
|
-
assert_equal before_open, open_connections, "the expired connection should be closed"
|
|
268
|
-
Backburner.configuration.enqueue_connection_max_idle = 60
|
|
269
|
-
assert_equal "INSERTED", enqueue(2)[:status]
|
|
270
|
-
assert_equal 1, total_connections - before_total, "a fresh connection should be reused"
|
|
271
|
-
ensure
|
|
272
|
-
Backburner.configuration.enqueue_connection_max_idle = max_idle
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
describe "when beanstalkd goes away" do
|
|
276
|
-
before do
|
|
277
|
-
pick_beanstalkd_port
|
|
278
|
-
@beanstalk_url = Backburner.configuration.beanstalk_url
|
|
279
|
-
Backburner.configuration.beanstalk_url = "beanstalk://127.0.0.1:#{@port}"
|
|
280
|
-
start_beanstalkd
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
after do
|
|
284
|
-
stop_beanstalkd
|
|
285
|
-
Backburner.configuration.beanstalk_url = @beanstalk_url if @beanstalk_url
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
it "should recover from a restart and from Beaneater::NotConnected" do
|
|
289
|
-
Thread.new do
|
|
290
|
-
assert_equal "INSERTED", Backburner::Worker.enqueue(TestJob, [1, 0])[:status]
|
|
291
|
-
stop_beanstalkd
|
|
292
|
-
start_beanstalkd
|
|
293
|
-
assert_equal "INSERTED", Backburner::Worker.enqueue(TestJob, [2, 0])[:status]
|
|
294
|
-
stop_beanstalkd
|
|
295
|
-
assert_raises(Beaneater::NotConnected) { Backburner::Worker.enqueue(TestJob, [3, 0]) }
|
|
296
|
-
start_beanstalkd
|
|
297
|
-
assert_equal "INSERTED", Backburner::Worker.enqueue(TestJob, [4, 0])[:status]
|
|
298
|
-
end.join
|
|
299
|
-
end
|
|
300
|
-
end # beanstalkd goes away
|
|
301
|
-
end # enqueue connection
|
|
302
|
-
|
|
303
|
-
describe "without a tube_namespace" do
|
|
304
|
-
before { @tube_namespace = Backburner.configuration.tube_namespace }
|
|
305
|
-
after { Backburner.configuration.tube_namespace = @tube_namespace }
|
|
306
|
-
|
|
307
|
-
def reserve_from(tube_name)
|
|
308
|
-
connection = beanstalk_connection
|
|
309
|
-
connection.tubes.watch!(tube_name)
|
|
310
|
-
job = connection.tubes.reserve(1)
|
|
311
|
-
job.delete
|
|
312
|
-
Backburner.configuration.job_parser_proc.call(job.body)
|
|
313
|
-
ensure
|
|
314
|
-
connection.close if connection
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
it "should enqueue on unprefixed tubes when tube_namespace is nil or false" do
|
|
318
|
-
[nil, false].each do |namespace|
|
|
319
|
-
Backburner.configuration.tube_namespace = namespace
|
|
320
|
-
Backburner::Worker.enqueue TestJob, [1, namespace.inspect]
|
|
321
|
-
Backburner::Worker.enqueue TestJob, [2, namespace.inspect], :queue => "test-no-namespace"
|
|
322
|
-
assert_equal [1, namespace.inspect], reserve_from("backburner-jobs")["args"]
|
|
323
|
-
assert_equal [2, namespace.inspect], reserve_from("test-no-namespace")["args"]
|
|
324
|
-
end
|
|
325
|
-
end
|
|
326
|
-
|
|
327
|
-
it "should enqueue on the tubes 1.7.0 used when tube_namespace is empty" do
|
|
328
|
-
Backburner.configuration.tube_namespace = ""
|
|
329
|
-
Backburner::Worker.enqueue TestJob, [1, 2]
|
|
330
|
-
Backburner::Worker.enqueue TestJob, [3, 4], :queue => "test-no-namespace"
|
|
331
|
-
assert_equal [1, 2], reserve_from(".backburner-jobs")["args"]
|
|
332
|
-
assert_equal [3, 4], reserve_from(".test-no-namespace")["args"]
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
it "should list existing unprefixed tubes for a worker given no tubes" do
|
|
336
|
-
Backburner.configuration.tube_namespace = false
|
|
337
|
-
Backburner::Worker.enqueue TestJob, [1, 2], :queue => "test-no-namespace"
|
|
338
|
-
assert_contains Backburner::Worker.new(nil).tube_names, "test-no-namespace"
|
|
339
|
-
reserve_from("test-no-namespace")
|
|
340
|
-
end
|
|
341
|
-
end # without a tube_namespace
|
|
342
|
-
|
|
343
|
-
describe "for a worker given no tubes" do
|
|
344
|
-
before do
|
|
345
|
-
@tube_namespace = Backburner.configuration.tube_namespace
|
|
346
|
-
@namespace_separator = Backburner.configuration.namespace_separator
|
|
347
|
-
end
|
|
348
|
-
after do
|
|
349
|
-
Backburner.configuration.tube_namespace = @tube_namespace
|
|
350
|
-
Backburner.configuration.namespace_separator = @namespace_separator
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
# Namespaces, separators and queue names (ActiveJob's queue_name_prefix gives "production_mailers") whose
|
|
354
|
-
# tubes the worker used to miss: it expanded the tube names it found on the server again ("a--b" raised)
|
|
355
|
-
it "should watch the tubes enqueued on for its namespace, under their names on the server" do
|
|
356
|
-
[["MyApp", ".", "default"], ["jobs(eu)", ".", "default"], ["app+", ".", "default"], ["bb-discovery", "_", "default"],
|
|
357
|
-
["production", ".", "production_mailers"], ["bb-discovery", ".", "report_2026"], ["bb-discovery", ".", "a__b"]].each do |namespace, separator, queue|
|
|
358
|
-
Backburner.configuration.tube_namespace = namespace
|
|
359
|
-
Backburner.configuration.namespace_separator = separator
|
|
360
|
-
tube_name = Backburner::Worker.expand_tube_name(queue)
|
|
361
|
-
connection = beanstalk_connection # no jobs left in this namespace for the worker to take first
|
|
362
|
-
connection.tubes.all.select { |tube| tube.name.start_with?("#{namespace}#{separator}") }.each(&:clear)
|
|
363
|
-
connection.close
|
|
364
|
-
Backburner::Worker.enqueue TestJob, [1, 2], :queue => queue
|
|
365
|
-
|
|
366
|
-
[Backburner::Workers::Simple, Backburner::Workers::Forking, Backburner::Workers::Threading, Backburner::Workers::ThreadsOnFork].each do |worker_class|
|
|
367
|
-
worker = worker_class.new(nil)
|
|
368
|
-
capture_stdout { worker.prepare }
|
|
369
|
-
worker.connection.close
|
|
370
|
-
assert_contains worker.tube_names, tube_name, "#{worker_class.name} #{[namespace, separator, queue].inspect}"
|
|
371
|
-
end
|
|
372
|
-
$worker_test_count = 0
|
|
373
|
-
worker = Backburner::Workers::Simple.new(nil)
|
|
374
|
-
silenced(3) { worker.prepare; worker.work_one_job }
|
|
375
|
-
worker.connection.close
|
|
376
|
-
assert_equal 3, $worker_test_count, [namespace, separator, queue].inspect
|
|
377
|
-
end
|
|
378
|
-
end
|
|
379
|
-
|
|
380
|
-
# Another app's tubes on a shared server: the worker expanded them into tubes nobody enqueues on, so it never took
|
|
381
|
-
# their jobs, and mustn't start to
|
|
382
|
-
it "should leave out the tubes its namespace and separator can't give" do
|
|
383
|
-
[[["bb-discovery_admin", "_"], ["bb-discovery", "_"]], [["MyApp.Admin", "."], ["MyApp", "."]],
|
|
384
|
-
[["bb-other", "."], ["bb:disc", "."]]].each do |(other_namespace, other_separator), (namespace, separator)|
|
|
385
|
-
Backburner.configuration.tube_namespace = other_namespace
|
|
386
|
-
Backburner.configuration.namespace_separator = other_separator
|
|
387
|
-
other_tube = Backburner::Worker.expand_tube_name("reports")
|
|
388
|
-
begin
|
|
389
|
-
Backburner::Worker.enqueue TestJob, [1, 2], :queue => "reports"
|
|
390
|
-
Backburner.configuration.tube_namespace = namespace
|
|
391
|
-
Backburner.configuration.namespace_separator = separator
|
|
392
|
-
worker = Backburner::Workers::Simple.new(nil)
|
|
393
|
-
capture_stdout { worker.prepare }
|
|
394
|
-
worker.connection.close
|
|
395
|
-
refute_includes worker.tube_names, other_tube, namespace
|
|
396
|
-
assert_equal ["bb"], worker.tube_names, namespace if namespace == "bb:disc" # a ':' cuts every tube name to "bb"
|
|
397
|
-
ensure
|
|
398
|
-
connection = beanstalk_connection
|
|
399
|
-
connection.tubes.find(other_tube).clear
|
|
400
|
-
connection.close
|
|
401
|
-
end
|
|
402
|
-
end
|
|
403
|
-
end
|
|
404
|
-
end # given no tubes
|
|
405
|
-
|
|
406
|
-
describe "for start class method" do
|
|
407
|
-
it "should initialize and start the worker instance" do
|
|
408
|
-
ech = stub
|
|
409
|
-
Backburner::Worker.expects(:new).with("foo").returns(ech)
|
|
410
|
-
ech.expects(:start)
|
|
411
|
-
Backburner::Worker.start("foo")
|
|
412
|
-
end
|
|
413
|
-
end # start
|
|
414
|
-
|
|
415
|
-
describe "for gc_disabled_on_exit? class method" do
|
|
416
|
-
it "should be true only on CRuby 3.3, the Ruby whose forked children can crash as they exit" do
|
|
417
|
-
assert Backburner::Worker.gc_disabled_on_exit?("ruby", "3.3.6")
|
|
418
|
-
%w(2.7.8 3.2.6 3.4.1 4.0.0).each do |version|
|
|
419
|
-
refute Backburner::Worker.gc_disabled_on_exit?("ruby", version), version
|
|
420
|
-
end
|
|
421
|
-
refute Backburner::Worker.gc_disabled_on_exit?("jruby", "3.3.0")
|
|
422
|
-
end
|
|
423
|
-
end # gc_disabled_on_exit?
|
|
424
|
-
|
|
425
|
-
describe "with an anonymous subclass of a queue class" do
|
|
426
|
-
before { @anonymous = Class.new(TestNamedQueueJob) }
|
|
427
|
-
after { Backburner::Worker.known_queue_classes.delete(@anonymous) }
|
|
428
|
-
|
|
429
|
-
it "should leave it out of the known queues of a worker given no tubes" do
|
|
430
|
-
[Backburner::Workers::Simple, Backburner::Workers::Threading, Backburner::Workers::ThreadsOnFork].each do |worker_class|
|
|
431
|
-
assert_contains worker_class.new(nil).tube_names.map { |name| Backburner::Worker.expand_tube_name(name) },
|
|
432
|
-
Backburner::Worker.expand_tube_name("testnamedqueuejob"), worker_class.name
|
|
433
|
-
end
|
|
434
|
-
end
|
|
435
|
-
end # anonymous subclass
|
|
436
|
-
|
|
437
|
-
describe "for tube_names accessor" do
|
|
438
|
-
before do
|
|
439
|
-
Backburner.default_queues << "baz"
|
|
440
|
-
Backburner.default_queues << "bam"
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
it "supports retrieving tubes" do
|
|
444
|
-
worker = Backburner::Worker.new(["foo", "bar"])
|
|
445
|
-
assert_equal ["foo", "bar"], worker.tube_names
|
|
446
|
-
end
|
|
447
|
-
|
|
448
|
-
it "supports single tube array arg" do
|
|
449
|
-
worker = Backburner::Worker.new([["foo", "bar"]])
|
|
450
|
-
assert_equal ["foo", "bar"], worker.tube_names
|
|
451
|
-
end
|
|
452
|
-
|
|
453
|
-
it "supports empty nil array arg with default values" do
|
|
454
|
-
worker = Backburner::Worker.new([nil])
|
|
455
|
-
assert_equal ['baz', 'bam'], worker.tube_names
|
|
456
|
-
end
|
|
457
|
-
|
|
458
|
-
it "supports single tube arg" do
|
|
459
|
-
worker = Backburner::Worker.new("foo")
|
|
460
|
-
assert_equal ["foo"], worker.tube_names
|
|
461
|
-
end
|
|
462
|
-
|
|
463
|
-
it "supports empty array arg with default values" do
|
|
464
|
-
worker = Backburner::Worker.new([])
|
|
465
|
-
assert_equal ['baz', 'bam'], worker.tube_names
|
|
466
|
-
end
|
|
467
|
-
|
|
468
|
-
it "supports nil arg with default values" do
|
|
469
|
-
worker = Backburner::Worker.new(nil)
|
|
470
|
-
assert_equal ['baz', 'bam'], worker.tube_names
|
|
471
|
-
end
|
|
472
|
-
end # tube_names
|
|
473
|
-
|
|
474
|
-
describe "for custom serialization" do
|
|
475
|
-
before do
|
|
476
|
-
Backburner.configure do |config|
|
|
477
|
-
@old_parser = config.job_parser_proc
|
|
478
|
-
@old_serializer = config.job_serializer_proc
|
|
479
|
-
config.job_parser_proc = lambda { |body| Marshal.load(body) }
|
|
480
|
-
config.job_serializer_proc = lambda { |body| Marshal.dump(body) }
|
|
481
|
-
end
|
|
482
|
-
end
|
|
483
|
-
|
|
484
|
-
after do
|
|
485
|
-
clear_jobs!('test-plain')
|
|
486
|
-
Backburner.configure do |config|
|
|
487
|
-
config.job_parser_proc = @old_parser
|
|
488
|
-
config.job_serializer_proc = @old_serializer
|
|
489
|
-
end
|
|
490
|
-
end
|
|
491
|
-
|
|
492
|
-
it "should support enqueuing a job" do
|
|
493
|
-
Backburner::Worker.enqueue TestPlainJob, [7, 9], :ttr => 100, :pri => 2000
|
|
494
|
-
pop_one_job("test-plain") do |job, body|
|
|
495
|
-
assert_equal "TestPlainJob", body[:class]
|
|
496
|
-
assert_equal [7, 9], body[:args]
|
|
497
|
-
assert_equal 100, job.ttr
|
|
498
|
-
assert_equal 2000, job.pri
|
|
499
|
-
end
|
|
500
|
-
end
|
|
501
|
-
end # custom serialization
|
|
502
|
-
end # Backburner::Worker
|