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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ad41e83594bda50d1b504a4e0e5777c477e5586dc88faddbbad476a81afb34d4
|
|
4
|
+
data.tar.gz: f53ac6873638f40f627bbcd1326f50fbd642e5454bce2c083b809cdc6904863b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0384db95f53944823490c2105948153c8aac2d0fb5e8268c8c6f82895ae722bf293bb93f7a0536967a9ad91a138d37f02d92f4f2b0e6860a749cb830065dd668'
|
|
7
|
+
data.tar.gz: 1e6cbde19cd41a7540346d5d66af0537af1fc53baec32466790217b0643b610cbd7d17f8ea74b1ed6aedd55a26c152d798df86f4ef6b21ac11b5bc848db9d175
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## Version 1.9.0 (Nov 1 2026)
|
|
4
|
+
|
|
5
|
+
* The gem now ships only `lib/`, `bin/`, `README.md`, `HOOKS.md`, `CHANGELOG.md` and `LICENSE` (no tests, examples or CI files), and its rubygems.org page links the source, the changelog and the issue tracker. **Behaviour change for the gem's owners:** pushing a release to rubygems.org now requires MFA (`rubygems_mfa_required`); the release workflow's trusted publishing is unaffected. **What to do:** owners who push by hand need MFA enabled on their rubygems.org account
|
|
6
|
+
* Docs: the README's Simple Async Jobs example says a `Backburner::Performable` class's queue defaults to the primary queue (`backburner-jobs`), not one named after the class, and its Persistence examples show the JSON jobs are stored as (`null`, double quotes) on the class's queue
|
|
7
|
+
* Docs: the README says Backburner uses only the first of several `beanstalk_url`s (no client-side federation), and that the Threading worker's `queue:threads` CLI format needs `config.default_worker = Backburner::Workers::Threading`; `examples/god.rb` sets `QUEUE` (which the rake tasks read) instead of `QUEUES`; `examples/custom.rb` enqueues on the queue it works; CONTRIBUTING says the tests need beanstalkd on 127.0.0.1:11300; `backburner --help` shows `-q, --queues QUEUES`; dead links point to beanstalkd.github.io, rubygems.org and rubydoc.info; the 1.5.0 and 1.6.0 changelog entries are filled in from their commits, with their release dates
|
|
8
|
+
* Docs: the README has an Active Job section: setting the adapter, how queues map to tubes, starting workers with `-q` for the Active Job queues (a worker started without `-q` doesn't pick up a queue first used after it started), the TTR, how `retry_on` and `max_job_retries` add up, and using Active Job callbacks rather than Backburner's hooks
|
|
9
|
+
* Active Job: a job's TTR is now its class's `queue_respond_timeout` when it defines one (`def self.queue_respond_timeout; 3600; end`; subclasses inherit it), as for a `Backburner::Queue` job; before, every Active Job job got `config.respond_timeout` (120 seconds by default), so a job that ran longer was stopped with `Backburner::Job::JobTimeout` and retried or buried. Jobs of classes without one still get `config.respond_timeout`. The TTR is set when a job is enqueued, so jobs already queued, or enqueued by an app still on an older backburner, keep the TTR they had. **What to do:** give long-running Active Job classes a `queue_respond_timeout` longer than they take
|
|
10
|
+
* Rails apps: a worker now runs each job inside the app's reloader (`Rails.application.reloader.wrap`), as Rails does for its own Active Job jobs and as Sidekiq does. **Behaviour change:** `ActiveSupport::CurrentAttributes` (a `Current.user` or tenant a job sets) no longer leak into the next job the worker runs; the database connections a job uses go back to the pool when it finishes; Active Record's query cache is on while a job runs, as it is for requests and Active Job jobs; and in development a job runs the app's latest code after a file changes (a reload waits for the jobs running in other threads to finish). On Rails 7.0 and newer, an error a job raises is now also reported to `Rails.error`, as an Active Job job's error already was (including a job whose class this worker doesn't have, each time it's released), but not a `Backburner::Job::RetryJob`, nor the `SystemExit` of a worker stopped by `TERM` or `INT` (or of a job that calls `exit`): an error tracker subscribed to `Rails.error` that `config.on_error` also calls may get it twice. An Active Job job runs in the worker's wrap, so Active Job's own wrap inside it does nothing: it's still wrapped once, but on Rails 7.1 and newer its errors, like other jobs', are now reported with the source `"job.backburner"` instead of `"application.active_support"` (a `Rails.error` subscriber that filters on the source sees the new one). `config.on_error` and the `on_retry` and `on_bury` hooks run after the job's wrap ends, so they no longer see the `CurrentAttributes` the job set, and a database connection they use is taken outside the executor (the `on_failure` hooks still run inside it). Outside Rails nothing changes. **What to do:** nothing, unless a job relies on state an earlier job left in `CurrentAttributes` (pass it as an argument instead), a job waits for a change another process makes to the database (read it inside `ActiveRecord::Base.uncached { ... }`, as the query cache would return the first result again), or an `on_error` handler or retry or bury hook reads the job's `CurrentAttributes` (pass what it needs in the job's arguments, or read it in an `on_failure` hook)
|
|
11
|
+
* Fix "Worker exiting..." often not being logged when a worker stops on `TERM` or `INT`: the process could exit before the thread that logs it ran. The worker now waits up to a second for that line to be written before it exits. **What to do:** nothing
|
|
12
|
+
* `Backburner::Performable` jobs (enqueued with `async`, `handle_asynchronously` or `handle_static_asynchronously`) now call only methods the app defines on the class, its superclasses (such as `ActiveRecord::Base`) or the modules they include, private ones too. **Behaviour change:** a job naming a method the class only gets from Ruby itself (`Object`, `Kernel` and `BasicObject`, or for a class method `Class` and `Module`, including what libraries add to them, such as `to_s`, `frozen?`, `name` or `try`), one of Backburner's own (`queue`, `perform`), or `method_missing` now fails with `NoMethodError`, and is retried and then buried like any other failure. A class that overrides such a method (`def to_s`) still has its own called. **What to do:** if your app enqueues such a method (`User.async.name`), define a method on the class that calls it and enqueue that instead. Jobs already queued keep the method name they were enqueued with, so they fail the same way once the workers are upgraded: to run them, define that same method on the class (`def self.name; super; end`) and kick them from the buried list, or delete them and enqueue the new method
|
|
13
|
+
* Fix the Threading worker taking `shutdown_timeout` (10 seconds by default) to exit on `TERM` or `INT` when it had no jobs to work: its threads waited in `reserve` until the shutdown gave up on them and killed them. Each thread now waits for a job at most a second at a time (or `config.reserve_timeout`, if shorter; a negative one, which made beanstalkd wait forever, counts as a second), so an idle worker exits within about a second. A busy worker is unchanged: its in-flight jobs get up to `shutdown_timeout` to finish before their threads are killed. A job a thread reserves as the shutdown begins is worked like any in-flight job; one still running when its thread is killed is not lost: beanstalkd puts it back in the queue when the worker exits, and it runs again. **What to do:** nothing; an idle Threading worker now sends beanstalkd a `reserve-with-timeout` once a second per thread
|
|
14
|
+
|
|
3
15
|
## Version 1.8.0 (Oct 31 2026)
|
|
4
16
|
|
|
5
17
|
* Fix ThreadsOnFork and Forking children crashing on Ruby 3.3 with `[BUG] lazy sweeping underway when freeing object space` when they stopped on a `TERM` or `INT`, on an error, or when a job called `exit`, if a thread in the parent was running finalizers when the child was forked (an interpreter bug fixed in Ruby 3.4). On Ruby 3.3 a forked child now disables the GC as it exits, so its `at_exit` handlers (the app's, and those it inherited from the parent) run with the GC off, and a child running several threads stops its job threads before those handlers run (a job they were working on goes back to the queue and runs again). Other Rubies are unchanged. **What to do:** nothing; on Ruby 3.3 an `at_exit` handler that allocates a lot in a child now grows the child's memory until it exits
|
|
@@ -37,13 +49,21 @@
|
|
|
37
49
|
|
|
38
50
|
* Change `File.exists?`` to `File.exist?` as latest ruby versions have removed support (@ShrutiSaagar).
|
|
39
51
|
|
|
40
|
-
## Version 1.6.0 (
|
|
52
|
+
## Version 1.6.0 (Dec 30 2021)
|
|
41
53
|
|
|
42
|
-
*
|
|
54
|
+
* Allow per-job `queue_retry_delay`, `queue_retry_delay_proc` and `queue_max_job_retries` (@boncey in #162)
|
|
55
|
+
* Fix the beanstalkd links in the README (@a112121788 in #171)
|
|
43
56
|
|
|
44
|
-
## Version 1.5.0 (
|
|
57
|
+
## Version 1.5.0 (Sep 19 2018)
|
|
45
58
|
|
|
46
|
-
*
|
|
59
|
+
* Add `Backburner::Job::RetryJob`: raise it from a job to retry it without logging an error (@eltone in #159)
|
|
60
|
+
* Threading worker: shut down gracefully on `TERM` or `INT`, letting running jobs finish; a second signal stops it at once (@eltone in #157)
|
|
61
|
+
* Make the logger's job timings thread-safe (@eltone in #157)
|
|
62
|
+
* Log, retry and bury jobs whose class is unknown instead of raising while handling their failure (@eltone in #154)
|
|
63
|
+
* Fix Ruby warnings about a shadowed and an unused variable (@utilum in #153)
|
|
64
|
+
* Allow concurrent-ruby 1.x after 1.0.1 (`~> 1.0`, `>= 1.0.1`)
|
|
65
|
+
* Docs: fix the link to Adam Wiggins' beanstalkd comparison (@dplummer in #152); add a CI badge (@szTheory in #151)
|
|
66
|
+
* CI: test against Ruby 2.x (@nicolasleger in #150)
|
|
47
67
|
|
|
48
68
|
## Version 1.4.1 (June 10 2017)
|
|
49
69
|
|
data/README.md
CHANGED
|
@@ -57,7 +57,7 @@ Beanstalkd supports the following features out of the box:
|
|
|
57
57
|
| **Fast** | Processes thousands of jobs per second without breaking a sweat. |
|
|
58
58
|
| **Priorities** | Specify priority so important jobs can be processed quickly. |
|
|
59
59
|
| **Persistence** | Jobs are stored in memory for speed, but logged to disk for safe keeping. |
|
|
60
|
-
| **Federation** | Horizontal scalability provided through federation by the client. |
|
|
60
|
+
| **Federation** | Horizontal scalability provided through federation by the client (Backburner connects to a single beanstalkd: given several `beanstalk_url`s, it uses the first). |
|
|
61
61
|
| **Error Handling** | Bury any job which causes an error for later debugging and inspection.|
|
|
62
62
|
|
|
63
63
|
Keep in mind that these features are supported out of the box with beanstalk and require no special code within this gem to support.
|
|
@@ -70,6 +70,9 @@ Depending on your platform, this should be as simple as (for Ubuntu):
|
|
|
70
70
|
|
|
71
71
|
$ sudo apt-get install beanstalkd
|
|
72
72
|
|
|
73
|
+
beanstalkd has no authentication: anyone who can connect to it can add jobs, so make it reachable only
|
|
74
|
+
from your app's own hosts (bind it to `127.0.0.1` or a private network, or firewall its port).
|
|
75
|
+
|
|
73
76
|
Add this line to your application's Gemfile:
|
|
74
77
|
|
|
75
78
|
gem 'backburner'
|
|
@@ -238,7 +241,7 @@ includes `Backburner::Performable`. Async enqueuing works for both instance and
|
|
|
238
241
|
```ruby
|
|
239
242
|
class User
|
|
240
243
|
include Backburner::Performable
|
|
241
|
-
queue "user-jobs" # defaults to '
|
|
244
|
+
queue "user-jobs" # defaults to the primary queue, 'backburner-jobs'
|
|
242
245
|
queue_priority 500 # most urgent priority is 0
|
|
243
246
|
queue_respond_timeout 300 # number of seconds before job times out (minimum 1; 0 does not disable it)
|
|
244
247
|
|
|
@@ -263,6 +266,10 @@ This automatically enqueues a job for that user record that will run `activate`
|
|
|
263
266
|
Note that you can set the queue name and queue priority at the class level and
|
|
264
267
|
you are also able to pass `pri`, `ttr`, `delay` and `queue` directly as options into `async`.
|
|
265
268
|
|
|
269
|
+
The worker calls only methods your app defines on the class, its superclasses or the modules they include (private
|
|
270
|
+
ones too). A job naming a method every object or class has (`frozen?`, `instance_variable_get`, `name`, ...), or one
|
|
271
|
+
of Backburner's own (`queue`, `perform`), fails with `NoMethodError`.
|
|
272
|
+
|
|
266
273
|
The queue name used by default is `{namespace}.backburner-jobs` if not otherwise
|
|
267
274
|
specified.
|
|
268
275
|
|
|
@@ -372,12 +379,12 @@ example from above. We'll run the following code to create a job:
|
|
|
372
379
|
User.async.reset_password(@user.id)
|
|
373
380
|
```
|
|
374
381
|
|
|
375
|
-
The following JSON will be put on the `{namespace}.backburner-jobs`
|
|
382
|
+
The following JSON will be put on the `{namespace}.user-jobs` queue (the class's queue; `{namespace}.backburner-jobs` for a class without one):
|
|
376
383
|
|
|
377
384
|
``` javascript
|
|
378
385
|
{
|
|
379
|
-
|
|
380
|
-
|
|
386
|
+
"class": "User",
|
|
387
|
+
"args": [null, "reset_password", 123]
|
|
381
388
|
}
|
|
382
389
|
```
|
|
383
390
|
|
|
@@ -393,8 +400,8 @@ would be stored as:
|
|
|
393
400
|
|
|
394
401
|
``` javascript
|
|
395
402
|
{
|
|
396
|
-
|
|
397
|
-
|
|
403
|
+
"class": "User",
|
|
404
|
+
"args": [246, "activate", 987]
|
|
398
405
|
}
|
|
399
406
|
```
|
|
400
407
|
|
|
@@ -474,7 +481,7 @@ For more information on the threads_on_fork worker, check out the
|
|
|
474
481
|
|
|
475
482
|
#### More info: Threading Worker (thread-pool-based)
|
|
476
483
|
|
|
477
|
-
Configuration options for the Threading worker are similar to the threads_on_fork worker, sans the garbage option.
|
|
484
|
+
Configuration options for the Threading worker are similar to the threads_on_fork worker, sans the garbage option. The `backburner` CLI runs the default worker, so set `config.default_worker = Backburner::Workers::Threading` in the app it loads; then it's simplest to provide the queue names and maximum number of threads in the format "{queue name}:{max threads in pool}[,{name}:{threads}]" (the Simple and Forking workers ignore the thread counts):
|
|
478
485
|
|
|
479
486
|
```
|
|
480
487
|
$ bundle exec backburner -q queue1:4,queue2:4 # and then other options, like environment, pidfile, app root, etc. See docs for the CLI
|
|
@@ -573,9 +580,58 @@ Often using hooks is much easier then trying to monkey patch the externals.
|
|
|
573
580
|
|
|
574
581
|
Check out [HOOKS.md](https://github.com/nesquena/backburner/blob/master/HOOKS.md) for a detailed overview on using hooks.
|
|
575
582
|
|
|
583
|
+
### Active Job
|
|
584
|
+
|
|
585
|
+
Backburner is an [Active Job](https://guides.rubyonrails.org/active_job_basics.html) adapter. Set it in
|
|
586
|
+
`config/application.rb` (or an environment file):
|
|
587
|
+
|
|
588
|
+
```ruby
|
|
589
|
+
config.active_job.queue_adapter = :backburner
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
An Active Job queue is a tube named like any other queue: the `tube_namespace`, the separator and the queue name,
|
|
593
|
+
dasherized. With the namespace `myapp.production`, `queue_as :low_priority` uses the tube `myapp.production.low-priority`;
|
|
594
|
+
a `queue_name_prefix` is part of the queue name (`production_mailers` uses `myapp.production.production-mailers`).
|
|
595
|
+
A job's priority (`queue_with_priority`, `set(priority: 10)`) is its beanstalkd priority (lower runs first), and
|
|
596
|
+
`set(wait: ...)` or `set(wait_until: ...)` delays it (rounded up to whole seconds).
|
|
597
|
+
|
|
598
|
+
Start workers with `-q` listing the Active Job queues, named as Active Job names them (with any prefix):
|
|
599
|
+
|
|
600
|
+
```
|
|
601
|
+
bundle exec backburner -q default,mailers,low_priority
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
A worker started without `-q` only watches the tubes that are on the server when it starts (and those of the
|
|
605
|
+
`Backburner::Queue` classes it has loaded, and the primary queue), so the jobs of a queue first used after it started
|
|
606
|
+
aren't worked until it restarts.
|
|
607
|
+
|
|
608
|
+
A job's time to run (TTR) is its class's `queue_respond_timeout` when it defines one, otherwise `config.respond_timeout`
|
|
609
|
+
(120 seconds by default). A job still running when its TTR is up is stopped with `Backburner::Job::JobTimeout`:
|
|
610
|
+
|
|
611
|
+
```ruby
|
|
612
|
+
class ReportJob < ApplicationJob
|
|
613
|
+
def self.queue_respond_timeout
|
|
614
|
+
3600 # seconds; subclasses inherit it
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
Active Job's `retry_on` and Backburner's `max_job_retries` add up: once `retry_on` gives up and the error reaches
|
|
620
|
+
Backburner, it retries the job `max_job_retries` more times before burying it, calling `on_error` each time. Keep
|
|
621
|
+
`max_job_retries` at 0 (the default) for Active Job jobs and retry with `retry_on`, or expect both.
|
|
622
|
+
|
|
623
|
+
Backburner's [hooks](#hooks) don't run for Active Job jobs (they are all enqueued as the adapter's wrapper class):
|
|
624
|
+
use Active Job's callbacks (`before_perform`, `around_perform`, `after_enqueue` and so on) instead. `config.on_error`
|
|
625
|
+
is still called when a job raises.
|
|
626
|
+
|
|
627
|
+
In a Rails app, a worker runs each job (Active Job or not) in the app's reloader, as Rails does: `CurrentAttributes` are
|
|
628
|
+
reset between jobs, a job's database connections go back to the pool when it's done, Active Record's query cache is on
|
|
629
|
+
while a job runs (a job that waits for a change another process makes reads it in `ActiveRecord::Base.uncached { ... }`),
|
|
630
|
+
and in development jobs run the latest code (a reload waits for the jobs running in other threads).
|
|
631
|
+
|
|
576
632
|
### Workers in Production
|
|
577
633
|
|
|
578
|
-
Once you have Backburner setup in your application, starting workers is really easy. Once [beanstalkd](
|
|
634
|
+
Once you have Backburner setup in your application, starting workers is really easy. Once [beanstalkd](https://beanstalkd.github.io/download.html)
|
|
579
635
|
is installed, your best bet is to use the built-in rake task that comes with Backburner. Simply add the task to your Rakefile:
|
|
580
636
|
|
|
581
637
|
```ruby
|
|
@@ -674,8 +730,8 @@ Thanks to these projects for inspiration and certain design and implementation d
|
|
|
674
730
|
|
|
675
731
|
## Links
|
|
676
732
|
|
|
677
|
-
* Code: `git clone
|
|
678
|
-
* Home: <
|
|
679
|
-
* Docs: <
|
|
680
|
-
* Bugs: <
|
|
681
|
-
* Gems: <
|
|
733
|
+
* Code: `git clone https://github.com/nesquena/backburner.git`
|
|
734
|
+
* Home: <https://github.com/nesquena/backburner>
|
|
735
|
+
* Docs: <https://www.rubydoc.info/gems/backburner>
|
|
736
|
+
* Bugs: <https://github.com/nesquena/backburner/issues>
|
|
737
|
+
* Gems: <https://rubygems.org/gems/backburner>
|
|
@@ -15,16 +15,23 @@ module ActiveJob
|
|
|
15
15
|
# To use Backburner set the queue_adapter config to +:backburner+.
|
|
16
16
|
#
|
|
17
17
|
# Rails.application.config.active_job.queue_adapter = :backburner
|
|
18
|
+
#
|
|
19
|
+
# A job's TTR is its class's +queue_respond_timeout+ when it has one, as for a Backburner::Queue job
|
|
20
|
+
# (+ttr: job.class+ resolves it), else +config.respond_timeout+:
|
|
21
|
+
#
|
|
22
|
+
# class ReportJob < ApplicationJob
|
|
23
|
+
# def self.queue_respond_timeout; 3600; end
|
|
24
|
+
# end
|
|
18
25
|
class BackburnerAdapter < (rails_7_2_or_newer ? ::ActiveJob::QueueAdapters::AbstractAdapter : Object)
|
|
19
26
|
def enqueue(job)
|
|
20
|
-
response = Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority)
|
|
27
|
+
response = Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority, ttr: job.class)
|
|
21
28
|
job.provider_job_id = response[:id] if response.is_a?(Hash)
|
|
22
29
|
response
|
|
23
30
|
end
|
|
24
31
|
|
|
25
32
|
def enqueue_at(job, timestamp)
|
|
26
33
|
delay = timestamp - Time.current.to_f
|
|
27
|
-
response = Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority, delay: delay)
|
|
34
|
+
response = Backburner::Worker.enqueue(JobWrapper, [job.serialize], queue: job.queue_name, pri: job.priority, delay: delay, ttr: job.class)
|
|
28
35
|
job.provider_job_id = response[:id] if response.is_a?(Hash)
|
|
29
36
|
response
|
|
30
37
|
end
|
data/lib/backburner/cli.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Backburner
|
|
|
10
10
|
opts.on("-r", "--require PATH", String, "The path to load as the environment.") do |req|
|
|
11
11
|
options[:require] = req
|
|
12
12
|
end
|
|
13
|
-
opts.on("-q", "--queues
|
|
13
|
+
opts.on("-q", "--queues QUEUES", String, "The specific queues to work (comma-separated).") do |queues|
|
|
14
14
|
options[:queues] = queues
|
|
15
15
|
end
|
|
16
16
|
opts.on("-e", "--environment ENVIRONMENT", String, "The environment to run Backburner within") do |environment|
|
|
@@ -9,6 +9,8 @@ module Backburner
|
|
|
9
9
|
class EnqueuePool
|
|
10
10
|
MAX_IDLE_CONNECTIONS = 5
|
|
11
11
|
|
|
12
|
+
@current = nil
|
|
13
|
+
|
|
12
14
|
# The pool of this process: a forked child gets a new one and leaves its parent's
|
|
13
15
|
# sockets (and a lock another thread may have held at the fork) alone.
|
|
14
16
|
# ceiling: no lock, so threads that race on the first enqueue (or the first after a fork) may
|
|
@@ -28,14 +28,16 @@ module Backburner
|
|
|
28
28
|
Backburner::AsyncProxy.new(self, nil, opts)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
# Defines perform method for job processing
|
|
31
|
+
# Defines perform method for job processing. It calls a method the app defines on the class,
|
|
32
|
+
# its superclasses or the modules they include, and raises NoMethodError for any other.
|
|
32
33
|
# @example
|
|
33
34
|
# perform(55, :do_something, "foo", "bar")
|
|
34
35
|
def perform(id, method, *args)
|
|
35
36
|
if id # instance
|
|
36
|
-
find(id)
|
|
37
|
+
record = find(id)
|
|
38
|
+
Backburner::Performable.perform_job_method(record, record.class, Object, method, args)
|
|
37
39
|
else # class method
|
|
38
|
-
|
|
40
|
+
Backburner::Performable.perform_job_method(self, singleton_class, Object.singleton_class, method, args)
|
|
39
41
|
end
|
|
40
42
|
end # perform
|
|
41
43
|
|
|
@@ -90,6 +92,29 @@ module Backburner
|
|
|
90
92
|
end
|
|
91
93
|
private_class_method :_handle_asynchronously
|
|
92
94
|
|
|
95
|
+
# Calls a job's method on `receiver`. It raises NoMethodError when the method isn't one the app
|
|
96
|
+
# defined: one that `methods_of` (the receiver's class, or the singleton class for a class method)
|
|
97
|
+
# gets from `core` (Object, or Object's singleton class for a class method) or the modules mixed
|
|
98
|
+
# into it, from Backburner's own modules, or Ruby's method_missing hook. A method with the same name
|
|
99
|
+
# as one of core's must be the app's own definition: one that method_missing would handle, or one
|
|
100
|
+
# from Delegator's copy of Kernel (the module delegate.rb aliases __raise__ in), is refused too.
|
|
101
|
+
def self.perform_job_method(receiver, methods_of, core, method, args)
|
|
102
|
+
owner = begin
|
|
103
|
+
methods_of.instance_method(method).owner
|
|
104
|
+
rescue NameError
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
refused = if owner && (core.ancestors.include?(owner) || [InstanceMethods, ClassMethods, Backburner::Queue::ClassMethods].include?(owner))
|
|
108
|
+
true
|
|
109
|
+
elsif core.method_defined?(method) || core.private_method_defined?(method)
|
|
110
|
+
owner.nil? || method.to_sym == :method_missing || (defined?(::Delegator) && owner.equal?(::Delegator.instance_method(:__raise__).owner))
|
|
111
|
+
end
|
|
112
|
+
if refused
|
|
113
|
+
kind = methods_of.singleton_class? ? "class method" : "method"
|
|
114
|
+
raise NoMethodError.new("undefined job #{kind} '#{method}' for #{methods_of.singleton_class? ? receiver : methods_of}", method.to_sym, receiver: receiver)
|
|
115
|
+
end
|
|
116
|
+
receiver.__send__(method, *args)
|
|
117
|
+
end
|
|
93
118
|
|
|
94
119
|
end # Performable
|
|
95
120
|
end # Backburner
|
data/lib/backburner/version.rb
CHANGED
data/lib/backburner/worker.rb
CHANGED
|
@@ -73,6 +73,7 @@ module Backburner
|
|
|
73
73
|
# @example
|
|
74
74
|
# Worker.new(['test.job'])
|
|
75
75
|
def initialize(tube_names=nil)
|
|
76
|
+
@existing_tube_names = nil # set by all_existing_queues, read by expand_tube_name
|
|
76
77
|
@connection = new_connection
|
|
77
78
|
@tube_names = self.process_tube_names(tube_names)
|
|
78
79
|
register_signal_handlers!
|
|
@@ -104,9 +105,12 @@ module Backburner
|
|
|
104
105
|
|
|
105
106
|
# Triggers this worker to shutdown
|
|
106
107
|
def shutdown
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
# Logs from another thread, since a trap handler can't take a logger's lock, and waits for it (a
|
|
109
|
+
# second at most): the process exits right after, and an unjoined thread never got to run
|
|
110
|
+
# ceiling: if the signal lands while the main thread holds the logger's lock, the line is lost after a
|
|
111
|
+
# second; handling the signal outside the trap (the self-pipe Threading uses) would lift it
|
|
112
|
+
Thread.new { log_info 'Worker exiting...' }.join(1)
|
|
113
|
+
ensure
|
|
110
114
|
Kernel.exit
|
|
111
115
|
end
|
|
112
116
|
|
|
@@ -136,7 +140,7 @@ module Backburner
|
|
|
136
140
|
end
|
|
137
141
|
|
|
138
142
|
self.log_job_begin(job.name, job.args)
|
|
139
|
-
job.process
|
|
143
|
+
wrap_in_rails_reloader { job.process }
|
|
140
144
|
self.log_job_end(job.name)
|
|
141
145
|
|
|
142
146
|
rescue Backburner::Job::JobFormatInvalid => e
|
|
@@ -203,6 +207,36 @@ module Backburner
|
|
|
203
207
|
Connection.new(Backburner.configuration.beanstalk_url) { |conn| Backburner::Hooks.invoke_hook_events(self, :on_reconnect, conn) }
|
|
204
208
|
end
|
|
205
209
|
|
|
210
|
+
# Runs the block (a job's processing) in the Rails app's reloader when there is one, as Rails does for Active Job
|
|
211
|
+
# jobs (whose own wrap, inside this one, then does nothing): each job starts with fresh CurrentAttributes, gives
|
|
212
|
+
# back its database connections when it's done and, in development, runs the app's latest code. On Rails 7.0+ the
|
|
213
|
+
# wrap reports the errors that leave it to Rails.error (with the source "job.backburner" on 7.1+); a RetryJob, a
|
|
214
|
+
# request to retry rather than a failure, and a SystemExit (a TERM or INT the worker stops on, or a job calling
|
|
215
|
+
# exit), which Rails 7.2+ would report too, are raised again outside it instead.
|
|
216
|
+
# ceiling: config.on_error and the on_retry and on_bury hooks run after the wrap ends, so they don't see the job's
|
|
217
|
+
# CurrentAttributes, and a database connection they use is outside the executor; running work_one_job's failure
|
|
218
|
+
# handling inside the wrap would lift it. In development, a Forking or ThreadsOnFork parent never runs the wrap, so
|
|
219
|
+
# after a file change each child reloads the app once.
|
|
220
|
+
def wrap_in_rails_reloader
|
|
221
|
+
app = ::Rails.application if defined?(::Rails) && ::Rails.respond_to?(:application)
|
|
222
|
+
return yield unless app.respond_to?(:reloader)
|
|
223
|
+
deferred = nil
|
|
224
|
+
work = proc do
|
|
225
|
+
begin
|
|
226
|
+
yield
|
|
227
|
+
rescue Backburner::Job::RetryJob, SystemExit => e
|
|
228
|
+
deferred = e
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
# Active Support 7.1+ take the source the executor reports errors with; older ones take no options
|
|
232
|
+
if ActiveSupport.gem_version >= Gem::Version.new("7.1")
|
|
233
|
+
app.reloader.wrap(source: "job.backburner", &work)
|
|
234
|
+
else
|
|
235
|
+
app.reloader.wrap(&work)
|
|
236
|
+
end
|
|
237
|
+
raise deferred if deferred
|
|
238
|
+
end
|
|
239
|
+
|
|
206
240
|
# Reserve a job from the watched queues
|
|
207
241
|
def reserve_job(conn, reserve_timeout = Backburner.configuration.reserve_timeout)
|
|
208
242
|
Backburner::Job.new(conn.tubes.reserve(reserve_timeout))
|
|
@@ -7,6 +7,9 @@ module Backburner
|
|
|
7
7
|
|
|
8
8
|
@shutdown_timeout = 10
|
|
9
9
|
|
|
10
|
+
# The longest a pool thread waits in reserve at a time (beanstalkd takes whole seconds)
|
|
11
|
+
SHUTDOWN_CHECK_INTERVAL = 1
|
|
12
|
+
|
|
10
13
|
class << self
|
|
11
14
|
attr_accessor :threads_number
|
|
12
15
|
attr_accessor :shutdown_timeout
|
|
@@ -15,6 +18,7 @@ module Backburner
|
|
|
15
18
|
# Custom initializer just to set @tubes_data
|
|
16
19
|
def initialize(*args)
|
|
17
20
|
@tubes_data = {}
|
|
21
|
+
@in_shutdown = false
|
|
18
22
|
super
|
|
19
23
|
self.process_tube_options
|
|
20
24
|
@exit_on_shutdown = true
|
|
@@ -152,6 +156,18 @@ module Backburner
|
|
|
152
156
|
end
|
|
153
157
|
end
|
|
154
158
|
end
|
|
159
|
+
|
|
160
|
+
protected
|
|
161
|
+
|
|
162
|
+
# Reserves for at most SHUTDOWN_CHECK_INTERVAL seconds, so an idle thread sees a shutdown
|
|
163
|
+
# and stops instead of waiting in reserve until shutdown_timeout kills it
|
|
164
|
+
# ceiling: each idle thread sends beanstalkd a reserve once a second; waking the waiting
|
|
165
|
+
# reserves at shutdown instead would avoid that polling
|
|
166
|
+
def reserve_job(conn, reserve_timeout = Backburner.configuration.reserve_timeout)
|
|
167
|
+
timeout = reserve_timeout.to_s.to_i if reserve_timeout # as beanstalkd reads what beaneater sends: "5", 2.5, 5.seconds
|
|
168
|
+
timeout = SHUTDOWN_CHECK_INTERVAL if timeout.nil? || timeout < 0 || timeout > SHUTDOWN_CHECK_INTERVAL
|
|
169
|
+
super(conn, timeout)
|
|
170
|
+
end
|
|
155
171
|
end # Threading
|
|
156
172
|
end # Workers
|
|
157
173
|
end # Backburner
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: backburner
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nathan Esquenazi
|
|
@@ -164,27 +164,11 @@ executables:
|
|
|
164
164
|
extensions: []
|
|
165
165
|
extra_rdoc_files: []
|
|
166
166
|
files:
|
|
167
|
-
- ".github/workflows/ci.yml"
|
|
168
|
-
- ".github/workflows/github-release.yml"
|
|
169
|
-
- ".github/workflows/release.yml"
|
|
170
|
-
- ".gitignore"
|
|
171
167
|
- CHANGELOG.md
|
|
172
|
-
- CONTRIBUTING.md
|
|
173
|
-
- Gemfile
|
|
174
168
|
- HOOKS.md
|
|
175
169
|
- LICENSE
|
|
176
170
|
- README.md
|
|
177
|
-
- Rakefile
|
|
178
|
-
- TODO
|
|
179
|
-
- backburner.gemspec
|
|
180
171
|
- bin/backburner
|
|
181
|
-
- examples/custom.rb
|
|
182
|
-
- examples/demo.rb
|
|
183
|
-
- examples/god.rb
|
|
184
|
-
- examples/hooked.rb
|
|
185
|
-
- examples/retried.rb
|
|
186
|
-
- examples/simple.rb
|
|
187
|
-
- examples/stress.rb
|
|
188
172
|
- lib/active_job/queue_adapters/backburner_adapter.rb
|
|
189
173
|
- lib/backburner.rb
|
|
190
174
|
- lib/backburner/async_proxy.rb
|
|
@@ -205,41 +189,14 @@ files:
|
|
|
205
189
|
- lib/backburner/workers/simple.rb
|
|
206
190
|
- lib/backburner/workers/threading.rb
|
|
207
191
|
- lib/backburner/workers/threads_on_fork.rb
|
|
208
|
-
|
|
209
|
-
- test/async_proxy_test.rb
|
|
210
|
-
- test/back_burner_test.rb
|
|
211
|
-
- test/connection_test.rb
|
|
212
|
-
- test/fixtures/active_job_app.rb
|
|
213
|
-
- test/fixtures/active_jobs.rb
|
|
214
|
-
- test/fixtures/autoload/test_broken_name_job.rb
|
|
215
|
-
- test/fixtures/autoload/test_broken_no_method_job.rb
|
|
216
|
-
- test/fixtures/autoload/test_broken_require_job.rb
|
|
217
|
-
- test/fixtures/autoload/test_broken_runtime_job.rb
|
|
218
|
-
- test/fixtures/autoload/test_broken_syntax_job.rb
|
|
219
|
-
- test/fixtures/autoload/test_broken_timeout_job.rb
|
|
220
|
-
- test/fixtures/autoload/test_same_name_job.rb
|
|
221
|
-
- test/fixtures/hooked.rb
|
|
222
|
-
- test/fixtures/test_fork_jobs.rb
|
|
223
|
-
- test/fixtures/test_forking_jobs.rb
|
|
224
|
-
- test/fixtures/test_jobs.rb
|
|
225
|
-
- test/fixtures/test_queue_settings.rb
|
|
226
|
-
- test/helpers/templogger.rb
|
|
227
|
-
- test/helpers_test.rb
|
|
228
|
-
- test/hooks_test.rb
|
|
229
|
-
- test/job_test.rb
|
|
230
|
-
- test/logger_test.rb
|
|
231
|
-
- test/performable_test.rb
|
|
232
|
-
- test/queue_test.rb
|
|
233
|
-
- test/test_helper.rb
|
|
234
|
-
- test/worker_test.rb
|
|
235
|
-
- test/workers/forking_worker_test.rb
|
|
236
|
-
- test/workers/simple_worker_test.rb
|
|
237
|
-
- test/workers/threading_worker_test.rb
|
|
238
|
-
- test/workers/threads_on_fork_worker_test.rb
|
|
239
|
-
homepage: http://github.com/nesquena/backburner
|
|
192
|
+
homepage: https://github.com/nesquena/backburner
|
|
240
193
|
licenses:
|
|
241
194
|
- MIT
|
|
242
|
-
metadata:
|
|
195
|
+
metadata:
|
|
196
|
+
source_code_uri: https://github.com/nesquena/backburner
|
|
197
|
+
changelog_uri: https://github.com/nesquena/backburner/blob/master/CHANGELOG.md
|
|
198
|
+
bug_tracker_uri: https://github.com/nesquena/backburner/issues
|
|
199
|
+
rubygems_mfa_required: 'true'
|
|
243
200
|
post_install_message:
|
|
244
201
|
rdoc_options: []
|
|
245
202
|
require_paths:
|
|
@@ -259,35 +216,4 @@ rubygems_version: 3.5.22
|
|
|
259
216
|
signing_key:
|
|
260
217
|
specification_version: 4
|
|
261
218
|
summary: Reliable beanstalk background job processing made easy for Ruby and Sinatra
|
|
262
|
-
test_files:
|
|
263
|
-
- test/active_job_adapter_test.rb
|
|
264
|
-
- test/async_proxy_test.rb
|
|
265
|
-
- test/back_burner_test.rb
|
|
266
|
-
- test/connection_test.rb
|
|
267
|
-
- test/fixtures/active_job_app.rb
|
|
268
|
-
- test/fixtures/active_jobs.rb
|
|
269
|
-
- test/fixtures/autoload/test_broken_name_job.rb
|
|
270
|
-
- test/fixtures/autoload/test_broken_no_method_job.rb
|
|
271
|
-
- test/fixtures/autoload/test_broken_require_job.rb
|
|
272
|
-
- test/fixtures/autoload/test_broken_runtime_job.rb
|
|
273
|
-
- test/fixtures/autoload/test_broken_syntax_job.rb
|
|
274
|
-
- test/fixtures/autoload/test_broken_timeout_job.rb
|
|
275
|
-
- test/fixtures/autoload/test_same_name_job.rb
|
|
276
|
-
- test/fixtures/hooked.rb
|
|
277
|
-
- test/fixtures/test_fork_jobs.rb
|
|
278
|
-
- test/fixtures/test_forking_jobs.rb
|
|
279
|
-
- test/fixtures/test_jobs.rb
|
|
280
|
-
- test/fixtures/test_queue_settings.rb
|
|
281
|
-
- test/helpers/templogger.rb
|
|
282
|
-
- test/helpers_test.rb
|
|
283
|
-
- test/hooks_test.rb
|
|
284
|
-
- test/job_test.rb
|
|
285
|
-
- test/logger_test.rb
|
|
286
|
-
- test/performable_test.rb
|
|
287
|
-
- test/queue_test.rb
|
|
288
|
-
- test/test_helper.rb
|
|
289
|
-
- test/worker_test.rb
|
|
290
|
-
- test/workers/forking_worker_test.rb
|
|
291
|
-
- test/workers/simple_worker_test.rb
|
|
292
|
-
- test/workers/threading_worker_test.rb
|
|
293
|
-
- test/workers/threads_on_fork_worker_test.rb
|
|
219
|
+
test_files: []
|
data/.github/workflows/ci.yml
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [ "master" ]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [ "master" ]
|
|
8
|
-
|
|
9
|
-
permissions:
|
|
10
|
-
contents: read
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
test:
|
|
14
|
-
name: test (${{ matrix.ruby-version }}${{ matrix.activejob-version && format(', activejob {0}', matrix.activejob-version) || '' }})
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
strategy:
|
|
17
|
-
fail-fast: false
|
|
18
|
-
matrix:
|
|
19
|
-
ruby-version: ['2.7', '3.0', '3.1', '3.2', '3.3', '3.4', '4.0']
|
|
20
|
-
activejob-version: [''] # the newest the Ruby version allows
|
|
21
|
-
include:
|
|
22
|
-
# Rails before 7.2 ship their own Active Job adapter and have no AbstractAdapter
|
|
23
|
-
- ruby-version: '3.1'
|
|
24
|
-
activejob-version: '6.1'
|
|
25
|
-
env:
|
|
26
|
-
ACTIVEJOB_VERSION: ${{ matrix.activejob-version }} # read by the Gemfile
|
|
27
|
-
|
|
28
|
-
steps:
|
|
29
|
-
- uses: actions/checkout@v4
|
|
30
|
-
- name: Download & Extract beanstalkd
|
|
31
|
-
run: curl -fsSL https://github.com/beanstalkd/beanstalkd/archive/refs/tags/v1.13.tar.gz | tar xz
|
|
32
|
-
- name: Make beanstalkd
|
|
33
|
-
run: make
|
|
34
|
-
working-directory: beanstalkd-1.13
|
|
35
|
-
- name: Set up Ruby
|
|
36
|
-
uses: ruby/setup-ruby@v1
|
|
37
|
-
with:
|
|
38
|
-
ruby-version: ${{ matrix.ruby-version }}
|
|
39
|
-
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
40
|
-
- name: Daemonize beanstalkd
|
|
41
|
-
run: ./beanstalkd-1.13/beanstalkd -l 127.0.0.1 -p 11300 &
|
|
42
|
-
- name: Run tests
|
|
43
|
-
run: bundle exec rake test
|
|
44
|
-
env:
|
|
45
|
-
BEANSTALKD_BIN: ${{ github.workspace }}/beanstalkd-1.13/beanstalkd # for the outage test, which stops and restarts its own server
|