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
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