backburner 1.0.0 → 1.1.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 +11 -1
- data/HOOKS.md +4 -0
- data/README.md +135 -30
- data/lib/backburner/cli.rb +9 -7
- data/lib/backburner/configuration.rb +35 -26
- data/lib/backburner/helpers.rb +7 -3
- data/lib/backburner/job.rb +12 -2
- data/lib/backburner/performable.rb +51 -0
- data/lib/backburner/queue.rb +1 -1
- data/lib/backburner/version.rb +1 -1
- data/lib/backburner/worker.rb +4 -3
- data/test/back_burner_test.rb +10 -0
- data/test/fixtures/hooked.rb +9 -1
- data/test/fixtures/test_jobs.rb +16 -1
- data/test/helpers_test.rb +20 -2
- data/test/hooks_test.rb +14 -0
- data/test/performable_test.rb +58 -8
- data/test/queue_test.rb +6 -1
- data/test/worker_test.rb +9 -1
- data/test/workers/simple_worker_test.rb +60 -2
- metadata +16 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f2543596fd5e337e0b4514ca94f8b32959c3901
|
4
|
+
data.tar.gz: eb4133d3c373307e9399636ce8c5c4df7630e74a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58857e61ce26ff416fa6b3732839adedc2b9230886cff2fafa812c444598b264525f141a48e9eb2294ff3d8b55bd105fb00c28a89fdf1eb7250ca74022a41567
|
7
|
+
data.tar.gz: dcbde0b50cc908dbb6052769a546c72589585a86b9d50ec5a4ceea1c6bdc9977430cfa45321593de52bb0e2f9048dd78b151f41de524be1b730b94bc64765606
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## Version 1.1.0 (September 14 2015)
|
4
|
+
|
5
|
+
* NEW Ability to configure namespace separator (@bfolkens)
|
6
|
+
* NEW Avoid timeouts altogether by setting queue_respond_timeout to 0 (@zacviandier)
|
7
|
+
* NEW Event hooks for on_retry and on_bury (@contentfree)
|
8
|
+
* NEW Support lambdas for queue names (@contentfree)
|
9
|
+
* NEW Allow for control of delay calculation (@contentfree)
|
10
|
+
* NEW Ability to specify environment when running the CLI (@contentfree)
|
11
|
+
* NEW Control default async behavior of methods (@contentfree)
|
12
|
+
|
3
13
|
## Version 1.0.0 (April 26 2015)
|
4
14
|
|
5
15
|
* NEW Updating to Beaneater 1.0 (@alup)
|
@@ -87,4 +97,4 @@ NOTE: This is the start of working with @bradgessler to improve backburner and m
|
|
87
97
|
## Version 0.1.0 (Nov 4 2012)
|
88
98
|
|
89
99
|
* Switch to beaneater as new ruby beanstalkd client
|
90
|
-
* Add support for array of connections in `beanstalk_url`
|
100
|
+
* Add support for array of connections in `beanstalk_url`
|
data/HOOKS.md
CHANGED
@@ -26,6 +26,10 @@ There are a variety of hooks available that are triggered during the lifecycle o
|
|
26
26
|
to perform the job (but is not required to do so). It may handle exceptions
|
27
27
|
thrown by perform, but uncaught exceptions will be treated like regular job exceptions.
|
28
28
|
|
29
|
+
* `on_retry`: Called with the retry count, the delay and the job args whenever a job is retried.
|
30
|
+
|
31
|
+
* `on_bury`: Called with the job args when the job is buried.
|
32
|
+
|
29
33
|
* `on_failure`: Called with the exception and job args if any exception occurs
|
30
34
|
while performing the job (or hooks).
|
31
35
|
|
data/README.md
CHANGED
@@ -88,35 +88,39 @@ Backburner is extremely simple to setup. Just configure basic settings for backb
|
|
88
88
|
|
89
89
|
```ruby
|
90
90
|
Backburner.configure do |config|
|
91
|
-
config.beanstalk_url
|
92
|
-
config.tube_namespace
|
93
|
-
config.
|
94
|
-
config.
|
95
|
-
config.
|
96
|
-
config.
|
97
|
-
config.
|
98
|
-
config.
|
99
|
-
config.
|
100
|
-
config.
|
101
|
-
config.
|
102
|
-
config.
|
91
|
+
config.beanstalk_url = ["beanstalk://127.0.0.1", "..."]
|
92
|
+
config.tube_namespace = "some.app.production"
|
93
|
+
config.namespace_separator = "."
|
94
|
+
config.on_error = lambda { |e| puts e }
|
95
|
+
config.max_job_retries = 3 # default 0 retries
|
96
|
+
config.retry_delay = 2 # default 5 seconds
|
97
|
+
config.retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) }
|
98
|
+
config.default_priority = 65536
|
99
|
+
config.respond_timeout = 120
|
100
|
+
config.default_worker = Backburner::Workers::Simple
|
101
|
+
config.logger = Logger.new(STDOUT)
|
102
|
+
config.primary_queue = "backburner-jobs"
|
103
|
+
config.priority_labels = { :custom => 50, :useless => 1000 }
|
104
|
+
config.reserve_timeout = nil
|
103
105
|
end
|
104
106
|
```
|
105
107
|
|
106
108
|
The key options available are:
|
107
109
|
|
108
|
-
| Option
|
109
|
-
| -----------------
|
110
|
-
| `beanstalk_url`
|
111
|
-
| `tube_namespace`
|
112
|
-
| `
|
113
|
-
| `
|
114
|
-
| `
|
115
|
-
| `
|
116
|
-
| `
|
117
|
-
| `
|
118
|
-
| `
|
119
|
-
| `
|
110
|
+
| Option | Description |
|
111
|
+
| ----------------- | ------------------------------- |
|
112
|
+
| `beanstalk_url` | Address such as 'beanstalk://127.0.0.1' or an array of addresses. |
|
113
|
+
| `tube_namespace` | Prefix used for all tubes related to this backburner queue. |
|
114
|
+
| `namespace_separator` | Separator used for namespace and queue name |
|
115
|
+
| `on_error` | Lambda invoked with the error whenever any job in the system fails. |
|
116
|
+
| `default_worker` | Worker class that will be used if no other worker is specified. |
|
117
|
+
| `max_job_retries` | Integer defines how many times to retry a job before burying. |
|
118
|
+
| `retry_delay` | Integer defines the base time to wait (in secs) between job retries. |
|
119
|
+
| `retry_delay_proc` | Lambda calculates the delay used, allowing for exponential back-off. |
|
120
|
+
| `logger` | Logger recorded to when backburner wants to report info or errors. |
|
121
|
+
| `primary_queue` | Primary queue used for a job when an alternate queue is not given. |
|
122
|
+
| `priority_labels` | Hash of named priority definitions for your app. |
|
123
|
+
| `reserve_timeout` | Duration to wait for work from a single server, or nil for forever. |
|
120
124
|
|
121
125
|
## Breaking Changes
|
122
126
|
|
@@ -154,7 +158,7 @@ class NewsletterJob
|
|
154
158
|
|
155
159
|
# optional, defaults to respond_timeout
|
156
160
|
def self.queue_respond_timeout
|
157
|
-
300 # number of seconds before job times out
|
161
|
+
300 # number of seconds before job times out, 0 to avoid timeout
|
158
162
|
end
|
159
163
|
end
|
160
164
|
```
|
@@ -166,7 +170,7 @@ class NewsletterJob
|
|
166
170
|
include Backburner::Queue
|
167
171
|
queue "newsletter-sender" # defaults to 'backburner-jobs' tube
|
168
172
|
queue_priority 1000 # most urgent priority is 0
|
169
|
-
queue_respond_timeout 300 # number of seconds before job times out
|
173
|
+
queue_respond_timeout 300 # number of seconds before job times out, 0 to avoid timeout
|
170
174
|
|
171
175
|
def self.perform(email, body)
|
172
176
|
NewsletterMailer.deliver_text_to_email(email, body)
|
@@ -184,6 +188,10 @@ Backburner.enqueue NewsletterJob, 'foo@admin.com', 'lorem ipsum...'
|
|
184
188
|
to that object's `perform` method. The queue name used by default is `{namespace}.backburner-jobs`
|
185
189
|
unless otherwise specified.
|
186
190
|
|
191
|
+
You may also pass a lambda as the queue name and it will be evaluated when enqueuing a
|
192
|
+
job (and passed the Job's class as an argument). This is especially useful when combined
|
193
|
+
with "Simple Async Jobs" (see below).
|
194
|
+
|
187
195
|
### Simple Async Jobs ###
|
188
196
|
|
189
197
|
In addition to defining custom jobs, a job can also be enqueued by invoking the `async` method on any object which
|
@@ -194,7 +202,7 @@ class User
|
|
194
202
|
include Backburner::Performable
|
195
203
|
queue "user-jobs" # defaults to 'user'
|
196
204
|
queue_priority 500 # most urgent priority is 0
|
197
|
-
queue_respond_timeout 300 # number of seconds before job times out
|
205
|
+
queue_respond_timeout 300 # number of seconds before job times out, 0 to avoid timeout
|
198
206
|
|
199
207
|
def activate(device_id)
|
200
208
|
@device = Device.find(device_id)
|
@@ -216,7 +224,55 @@ User.async(:pri => 100, :delay => 10.seconds).reset_password(@user.id)
|
|
216
224
|
This automatically enqueues a job for that user record that will run `activate` with the specified argument.
|
217
225
|
Note that you can set the queue name and queue priority at the class level and
|
218
226
|
you are also able to pass `pri`, `ttr`, `delay` and `queue` directly as options into `async`.
|
219
|
-
|
227
|
+
|
228
|
+
The queue name used by default is `{namespace}.backburner-jobs` if not otherwise
|
229
|
+
specified.
|
230
|
+
|
231
|
+
If a lambda is given for `queue`, then it will be called and given the
|
232
|
+
_performable_ object's class as an argument:
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
# Given the User class above
|
236
|
+
User.async(:queue => lambda { |user_klass| ["queue1","queue2"].sample(1).first }).do_hard_work # would add the job to either queue1 or queue2 randomly
|
237
|
+
```
|
238
|
+
|
239
|
+
### Using Async Asynchronously ###
|
240
|
+
|
241
|
+
It's often useful to be able to configure your app in production such that every invocation of a method is asynchronous by default as seen in [delayed_job](https://github.com/collectiveidea/delayed_job#queuing-jobs). To accomplish this, the `Backburner::Performable` module exposes two `handle_asynchronously` convenience methods
|
242
|
+
which accept the same options as the `async` method:
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
class User
|
246
|
+
include Backburner::Performable
|
247
|
+
|
248
|
+
def send_welcome_email
|
249
|
+
# ...
|
250
|
+
end
|
251
|
+
|
252
|
+
# ---> For instance methods
|
253
|
+
handle_asynchronously :send_welcome_email, queue: 'send-mail', pri: 5000, ttr: 60
|
254
|
+
|
255
|
+
def self.update_recent_visitors
|
256
|
+
# ...
|
257
|
+
end
|
258
|
+
|
259
|
+
# ---> For class methods
|
260
|
+
handle_static_asynchronously :update_recent_visitors, queue: 'long-tasks', ttr: 300
|
261
|
+
end
|
262
|
+
```
|
263
|
+
|
264
|
+
Now, all calls to `User.update_recent_visitors` or `User#send_welcome_email` will automatically be handled asynchronously when invoked. Similarly, you can call these methods directly on the `Backburner::Performable` module to apply async behavior outside the class:
|
265
|
+
|
266
|
+
```ruby
|
267
|
+
# Given the User class above
|
268
|
+
Backburner::Performable.handle_asynchronously(User, :activate, ttr: 100, queue: 'activate')
|
269
|
+
```
|
270
|
+
|
271
|
+
Now all calls to the `activate` method on a `User` instance will be async with the provided options.
|
272
|
+
|
273
|
+
#### A Note About Auto-Async
|
274
|
+
|
275
|
+
Because an async proxy is injected and used in place of the original method, you must not rely on the return value of the method. Using the example `User` class above, if my `send_welcome_email` returned the status of an email submission and I relied on that to take some further action, I will be surprised after rewiring things with `handle_asynchronously` because the async proxy actually returns the (boolean) result of `Backburner::Worker.enqueue`.
|
220
276
|
|
221
277
|
### Working Jobs
|
222
278
|
|
@@ -366,7 +422,9 @@ $ QUEUE=newsletter-sender,push-message THREADS=2 GARBAGE=1000 rake backburner:th
|
|
366
422
|
```
|
367
423
|
|
368
424
|
For more information on the threads_on_fork worker, check out the
|
369
|
-
[ThreadsOnFork Worker](https://github.com/nesquena/backburner/wiki/ThreadsOnFork-worker) documentation.
|
425
|
+
[ThreadsOnFork Worker](https://github.com/nesquena/backburner/wiki/ThreadsOnFork-worker) documentation. Please note that the `ThreadsOnFork` worker does not work on Windows due to its lack of `fork`.
|
426
|
+
|
427
|
+
|
370
428
|
Additional workers such as individual `threaded` and `forking` strategies will hopefully be contributed in the future.
|
371
429
|
If you are interested in helping out, please let us know.
|
372
430
|
|
@@ -401,7 +459,7 @@ The `default_queues` stores the specific list of queues that should be processed
|
|
401
459
|
### Failures
|
402
460
|
|
403
461
|
When a job fails in backburner (usually because an exception was raised), the job will be released
|
404
|
-
and retried again
|
462
|
+
and retried again until the `max_job_retries` configuration is reached.
|
405
463
|
|
406
464
|
```ruby
|
407
465
|
Backburner.configure do |config|
|
@@ -411,6 +469,19 @@ end
|
|
411
469
|
```
|
412
470
|
|
413
471
|
Note the default `max_job_retries` is 0, meaning that by default **jobs are not retried**.
|
472
|
+
|
473
|
+
As jobs are retried, a progressively-increasing delay is added to give time for transient
|
474
|
+
problems to resolve themselves. This may be configured using `retry_delay_proc`. It expects
|
475
|
+
an object that responds to `#call` and receives the value of `retry_delay` and the number
|
476
|
+
of times the job has been retried already. The default is a cubic back-off, eg:
|
477
|
+
|
478
|
+
```ruby
|
479
|
+
Backburner.configure do |config|
|
480
|
+
config.retry_delay = 2 # The minimum number of seconds a retry will be delayed
|
481
|
+
config.retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) }
|
482
|
+
end
|
483
|
+
```
|
484
|
+
|
414
485
|
If continued retry attempts fail, the job will be buried and can be 'kicked' later for inspection.
|
415
486
|
|
416
487
|
You can also setup a custom error handler for jobs using configure:
|
@@ -468,6 +539,39 @@ The best way to deploy these rake tasks is using a monitoring library. We sugges
|
|
468
539
|
which watches processes and ensures their stability. A simple God recipe for Backburner can be found in
|
469
540
|
[examples/god](https://github.com/nesquena/backburner/blob/master/examples/god.rb).
|
470
541
|
|
542
|
+
#### Command-Line Interface
|
543
|
+
|
544
|
+
Instead of using the Rake tasks, you can use Backburner's command-line interface (CLI) – powered by the [Dante gem](https://github.com/nesquena/dante) – to launch daemonized workers. Several flags are available to control the process. Many of these are provided by Dante itself, such as flags for logging (`-l`), the process' PID (`-P`), whether to daemonize (`-d`) or kill a running process (`-k`). Backburner provides a few more:
|
545
|
+
|
546
|
+
|
547
|
+
##### Queues (`-q`)
|
548
|
+
|
549
|
+
Control which queues the worker will watch with the `-q` flag. Comma-separate multiple queue names and, if you're using the `ThreadsOnFork` worker, colon-separate the settings for thread limit, garbage limit and retries limit (eg. `send_mail:4:10:3`). See its [wiki page](https://github.com/nesquena/backburner/wiki/ThreadsOnFork-worker) for some more details.
|
550
|
+
|
551
|
+
```ruby
|
552
|
+
backburner -q send_mail,create_thumbnail # You may need to use `bundle exec`
|
553
|
+
```
|
554
|
+
|
555
|
+
##### Boot an app (`-r`)
|
556
|
+
|
557
|
+
Load an app with the `-r` flag. Backburner supports automatic loading for both Rails and Padrino apps when started from the their root folder. However, you may point to a specific app's root using this flag, which is very useful when running workers from a service script.
|
558
|
+
|
559
|
+
```ruby
|
560
|
+
path="/var/www/my-app/current"
|
561
|
+
backburner -r "$path"
|
562
|
+
```
|
563
|
+
|
564
|
+
##### Load an environment (`-e`)
|
565
|
+
|
566
|
+
Use the `-e` flag to control which environment your app should use:
|
567
|
+
|
568
|
+
```ruby
|
569
|
+
environment="production"
|
570
|
+
backburner -e $environment
|
571
|
+
```
|
572
|
+
|
573
|
+
#### Reconnecting
|
574
|
+
|
471
575
|
In Backburner, if the beanstalkd connection is temporarily severed, several retries to establish the connection will be attempted.
|
472
576
|
After several retries, if the connection is still not able to be made, a `Beaneater::NotConnected` exception will be raised.
|
473
577
|
You can manually catch this exception, and attempt another manual retry using `Backburner::Worker.retry_connection!`.
|
@@ -481,6 +585,7 @@ jobs processed by your beanstalk workers. An excellent addition to your Backburn
|
|
481
585
|
## Acknowledgements
|
482
586
|
|
483
587
|
* [Nathan Esquenazi](https://github.com/nesquena) - Project maintainer
|
588
|
+
* [Dave Myron](https://github.com/contentfree) - Multiple features and doc improvements
|
484
589
|
* Kristen Tucker - Coming up with the gem name
|
485
590
|
* [Tim Lee](https://github.com/timothy1ee), [Josh Hull](https://github.com/joshbuddy), [Nico Taing](https://github.com/Nico-Taing) - Helping me work through the idea
|
486
591
|
* [Miso](http://gomiso.com) - Open-source friendly place to work
|
data/lib/backburner/cli.rb
CHANGED
@@ -13,19 +13,23 @@ module Backburner
|
|
13
13
|
opts.on("-q", "--queues PATH", String, "The specific queues to work.") do |queues|
|
14
14
|
options[:queues] = queues
|
15
15
|
end
|
16
|
+
opts.on("-e", "--environment ENVIRONMENT", String, "The environment to run Backburner within") do |environment|
|
17
|
+
options[:environment] = environment
|
18
|
+
end
|
16
19
|
end
|
17
20
|
runner.execute do |opts|
|
18
21
|
queues = (opts[:queues] ? opts[:queues].split(',') : nil) rescue nil
|
19
|
-
|
22
|
+
load_environment(opts[:require], opts[:environment])
|
20
23
|
Backburner.work(queues)
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
24
27
|
protected
|
25
28
|
|
26
|
-
def self.
|
29
|
+
def self.load_environment(file = nil, environment = nil)
|
27
30
|
file ||= "."
|
28
31
|
if File.directory?(file) && File.exists?(File.expand_path("#{file}/config/environment.rb"))
|
32
|
+
ENV["RAILS_ENV"] = environment if environment && ENV["RAILS_ENV"].nil?
|
29
33
|
require "rails"
|
30
34
|
require File.expand_path("#{file}/config/environment.rb")
|
31
35
|
if defined?(::Rails) && ::Rails.respond_to?(:application)
|
@@ -37,11 +41,9 @@ module Backburner
|
|
37
41
|
::Rails::Initializer.run :load_application_classes
|
38
42
|
end
|
39
43
|
elsif File.directory?(file) && File.exists?(File.expand_path("#{file}/config/boot.rb"))
|
40
|
-
if
|
41
|
-
|
42
|
-
|
43
|
-
require File.expand_path("#{file}/config/boot.rb")
|
44
|
-
end
|
44
|
+
ENV["RACK_ENV"] = environment if environment && ENV["RACK_ENV"].nil?
|
45
|
+
ENV["PADRINO_ROOT"] = file
|
46
|
+
require File.expand_path("#{file}/config/boot.rb")
|
45
47
|
elsif File.file?(file)
|
46
48
|
require File.expand_path(file)
|
47
49
|
end
|
@@ -2,34 +2,43 @@ module Backburner
|
|
2
2
|
class Configuration
|
3
3
|
PRIORITY_LABELS = { :high => 0, :medium => 100, :low => 200 }
|
4
4
|
|
5
|
-
attr_accessor :beanstalk_url
|
6
|
-
attr_accessor :tube_namespace
|
7
|
-
|
8
|
-
attr_accessor :
|
9
|
-
attr_accessor :
|
10
|
-
attr_accessor :
|
11
|
-
attr_accessor :
|
12
|
-
attr_accessor :
|
13
|
-
attr_accessor :
|
14
|
-
attr_accessor :
|
15
|
-
attr_accessor :
|
16
|
-
attr_accessor :
|
17
|
-
attr_accessor :
|
5
|
+
attr_accessor :beanstalk_url # beanstalk url connection
|
6
|
+
attr_accessor :tube_namespace # namespace prefix for every queue
|
7
|
+
attr_reader :namespace_separator # namespace separator
|
8
|
+
attr_accessor :default_priority # default job priority
|
9
|
+
attr_accessor :respond_timeout # default job timeout
|
10
|
+
attr_accessor :on_error # error handler
|
11
|
+
attr_accessor :max_job_retries # max job retries
|
12
|
+
attr_accessor :retry_delay # (minimum) retry delay in seconds
|
13
|
+
attr_accessor :retry_delay_proc # proc to calculate delay (and allow for back-off)
|
14
|
+
attr_accessor :default_queues # default queues
|
15
|
+
attr_accessor :logger # logger
|
16
|
+
attr_accessor :default_worker # default worker class
|
17
|
+
attr_accessor :primary_queue # the general queue
|
18
|
+
attr_accessor :priority_labels # priority labels
|
19
|
+
attr_accessor :reserve_timeout # duration to wait to reserve on a single server
|
18
20
|
|
19
21
|
def initialize
|
20
|
-
@beanstalk_url
|
21
|
-
@tube_namespace
|
22
|
-
@
|
23
|
-
@
|
24
|
-
@
|
25
|
-
@
|
26
|
-
@
|
27
|
-
@
|
28
|
-
@
|
29
|
-
@
|
30
|
-
@
|
31
|
-
@
|
32
|
-
@
|
22
|
+
@beanstalk_url = "beanstalk://localhost"
|
23
|
+
@tube_namespace = "backburner.worker.queue"
|
24
|
+
@namespace_separator = "."
|
25
|
+
@default_priority = 65536
|
26
|
+
@respond_timeout = 120
|
27
|
+
@on_error = nil
|
28
|
+
@max_job_retries = 0
|
29
|
+
@retry_delay = 5
|
30
|
+
@retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) }
|
31
|
+
@default_queues = []
|
32
|
+
@logger = nil
|
33
|
+
@default_worker = Backburner::Workers::Simple
|
34
|
+
@primary_queue = "backburner-jobs"
|
35
|
+
@priority_labels = PRIORITY_LABELS
|
36
|
+
@reserve_timeout = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def namespace_separator=(val)
|
40
|
+
raise 'Namespace separator cannot used reserved queue configuration separator ":"' if val == ':'
|
41
|
+
@namespace_separator = val
|
33
42
|
end
|
34
43
|
end # Configuration
|
35
44
|
end # Backburner
|
data/lib/backburner/helpers.rb
CHANGED
@@ -86,16 +86,20 @@ module Backburner
|
|
86
86
|
#
|
87
87
|
def expand_tube_name(tube)
|
88
88
|
prefix = queue_config.tube_namespace
|
89
|
+
separator = queue_config.namespace_separator
|
89
90
|
queue_name = if tube.is_a?(String)
|
90
91
|
tube
|
91
92
|
elsif tube.respond_to?(:queue) # use queue name
|
92
|
-
tube.queue
|
93
|
+
queue = tube.queue
|
94
|
+
queue.is_a?(Proc) ? queue.call(tube) : queue
|
95
|
+
elsif tube.is_a?(Proc)
|
96
|
+
tube.call
|
93
97
|
elsif tube.is_a?(Class) # no queue name, use default
|
94
98
|
queue_config.primary_queue # tube.name
|
95
99
|
else # turn into a string
|
96
100
|
tube.to_s
|
97
101
|
end
|
98
|
-
[prefix.gsub(/\.$/, ''), dasherize(queue_name).gsub(/^#{prefix}/, '')].join(
|
102
|
+
[prefix.gsub(/\.$/, ''), dasherize(queue_name).gsub(/^#{prefix}/, '')].join(separator).gsub(/#{Regexp::escape(separator)}+/, separator).split(':').first
|
99
103
|
end
|
100
104
|
|
101
105
|
# Resolves job priority based on the value given. Can be integer, a class or nothing
|
@@ -135,4 +139,4 @@ module Backburner
|
|
135
139
|
end
|
136
140
|
|
137
141
|
end # Helpers
|
138
|
-
end # Backburner
|
142
|
+
end # Backburner
|
data/lib/backburner/job.rb
CHANGED
@@ -19,10 +19,10 @@ module Backburner
|
|
19
19
|
# Backburner::Job.new(payload)
|
20
20
|
#
|
21
21
|
def initialize(task)
|
22
|
+
@hooks = Backburner::Hooks
|
22
23
|
@task = task
|
23
24
|
@body = task.body.is_a?(Hash) ? task.body : JSON.parse(task.body)
|
24
25
|
@name, @args = body["class"], body["args"]
|
25
|
-
@hooks = Backburner::Hooks
|
26
26
|
rescue => ex # Job was not valid format
|
27
27
|
self.bury
|
28
28
|
raise JobFormatInvalid, "Job body could not be parsed: #{ex.inspect}"
|
@@ -46,7 +46,7 @@ module Backburner
|
|
46
46
|
return false unless res
|
47
47
|
# Execute the job
|
48
48
|
@hooks.around_hook_events(job_class, :around_perform, *args) do
|
49
|
-
timeout_job_after(task.ttr
|
49
|
+
timeout_job_after(task.ttr) { job_class.perform(*args) }
|
50
50
|
end
|
51
51
|
task.delete
|
52
52
|
# Invoke after perform hook
|
@@ -56,6 +56,16 @@ module Backburner
|
|
56
56
|
raise e
|
57
57
|
end
|
58
58
|
|
59
|
+
def bury
|
60
|
+
@hooks.invoke_hook_events(job_class, :on_bury, *args)
|
61
|
+
task.bury
|
62
|
+
end
|
63
|
+
|
64
|
+
def retry(count, delay)
|
65
|
+
@hooks.invoke_hook_events(job_class, :on_retry, count, delay, *args)
|
66
|
+
task.release(delay: delay)
|
67
|
+
end
|
68
|
+
|
59
69
|
protected
|
60
70
|
|
61
71
|
# Returns the class for the job handler
|
@@ -38,7 +38,58 @@ module Backburner
|
|
38
38
|
send(method, *args)
|
39
39
|
end
|
40
40
|
end # perform
|
41
|
+
|
42
|
+
# Always handle an instance method asynchronously
|
43
|
+
# @example
|
44
|
+
# User.handle_asynchronously :send_welcome_email, queue: 'send-mail', delay: 10
|
45
|
+
def handle_asynchronously(method, opts={})
|
46
|
+
Backburner::Performable.handle_asynchronously(self, method, opts)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Always handle a class method asynchronously
|
50
|
+
# @example
|
51
|
+
# User.handle_static_asynchronously :update_recent_visitors, ttr: 300
|
52
|
+
def handle_static_asynchronously(method, opts={})
|
53
|
+
Backburner::Performable.handle_static_asynchronously(self, method, opts)
|
54
|
+
end
|
41
55
|
end # ClassMethods
|
42
56
|
|
57
|
+
|
58
|
+
# Make all calls to an instance method asynchronous. The given opts will be passed
|
59
|
+
# to the async method.
|
60
|
+
# @example
|
61
|
+
# Backburner::Performable.handle_asynchronously(MyObject, :long_task, queue: 'long-tasks')
|
62
|
+
# NB: The method called on the async proxy will be ""#{method}_without_async". This
|
63
|
+
# will also be what's given to the Worker.enqueue method so your workers need
|
64
|
+
# to know about that. It shouldn't be a problem unless the producer and consumer are
|
65
|
+
# from different codebases (or anywhere they don't both call the handle_asynchronously
|
66
|
+
# method when booting up)
|
67
|
+
def self.handle_asynchronously(klass, method, opts={})
|
68
|
+
_handle_asynchronously(klass, klass, method, opts)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Make all calls to a class method asynchronous. The given opts will be passed
|
72
|
+
# to the async method. Please see the NB on #handle_asynchronously
|
73
|
+
def self.handle_static_asynchronously(klass, method, opts={})
|
74
|
+
_handle_asynchronously(klass, klass.singleton_class, method, opts)
|
75
|
+
end
|
76
|
+
|
77
|
+
def self._handle_asynchronously(klass, klass_eval_scope, method, opts={})
|
78
|
+
aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1
|
79
|
+
with_async_name = :"#{aliased_method}_with_async#{punctuation}"
|
80
|
+
without_async_name = :"#{aliased_method}_without_async#{punctuation}"
|
81
|
+
|
82
|
+
klass.send(:include, Performable) unless included_modules.include?(Performable)
|
83
|
+
klass_eval_scope.class_eval do
|
84
|
+
define_method with_async_name do |*args|
|
85
|
+
async(opts).__send__ without_async_name, *args
|
86
|
+
end
|
87
|
+
alias_method without_async_name, method.to_sym
|
88
|
+
alias_method method.to_sym, with_async_name
|
89
|
+
end
|
90
|
+
end
|
91
|
+
private_class_method :_handle_asynchronously
|
92
|
+
|
93
|
+
|
43
94
|
end # Performable
|
44
95
|
end # Backburner
|
data/lib/backburner/queue.rb
CHANGED
data/lib/backburner/version.rb
CHANGED
data/lib/backburner/worker.rb
CHANGED
@@ -31,7 +31,8 @@ module Backburner
|
|
31
31
|
return false unless res # stop if hook is false
|
32
32
|
data = { :class => job_class.name, :args => args }
|
33
33
|
retryable_command do
|
34
|
-
|
34
|
+
queue = opts[:queue] && (Proc === opts[:queue] ? opts[:queue].call(job_class) : opts[:queue])
|
35
|
+
tube = connection.tubes[expand_tube_name(queue || job_class)]
|
35
36
|
tube.put(data.to_json, :pri => pri, :delay => delay, :ttr => ttr)
|
36
37
|
end
|
37
38
|
Backburner::Hooks.invoke_hook_events(job_class, :after_enqueue, *args)
|
@@ -139,8 +140,8 @@ module Backburner
|
|
139
140
|
num_retries = job.stats.releases
|
140
141
|
retry_status = "failed: attempt #{num_retries+1} of #{queue_config.max_job_retries+1}"
|
141
142
|
if num_retries < queue_config.max_job_retries # retry again
|
142
|
-
delay = queue_config.retry_delay
|
143
|
-
job.
|
143
|
+
delay = queue_config.retry_delay_proc.call(queue_config.retry_delay, num_retries) rescue queue_config.retry_delay
|
144
|
+
job.retry(num_retries + 1, delay)
|
144
145
|
self.log_job_end(job.name, "#{retry_status}, retrying in #{delay}s") if job_started_at
|
145
146
|
else # retries failed, bury
|
146
147
|
job.bury
|
data/test/back_burner_test.rb
CHANGED
@@ -62,6 +62,16 @@ describe "Backburner module" do
|
|
62
62
|
it "remembers the tube_namespace" do
|
63
63
|
assert_equal "demo.test", Backburner.configuration.tube_namespace
|
64
64
|
end
|
65
|
+
|
66
|
+
it "remembers the namespace_separator" do
|
67
|
+
assert_equal ".", Backburner.configuration.namespace_separator
|
68
|
+
end
|
69
|
+
|
70
|
+
it "disallows a reserved separator" do
|
71
|
+
assert_raises RuntimeError do
|
72
|
+
Backburner.configuration.namespace_separator = ':'
|
73
|
+
end
|
74
|
+
end
|
65
75
|
end # configuration
|
66
76
|
|
67
77
|
describe "for default_queues" do
|
data/test/fixtures/hooked.rb
CHANGED
@@ -100,9 +100,17 @@ class HookedObjectSuccess
|
|
100
100
|
puts "!!on_failure_foo!! #{ex.inspect} #{args.inspect}"
|
101
101
|
end
|
102
102
|
|
103
|
+
def self.on_bury_foo(*args)
|
104
|
+
puts "!!on_bury_foo!! #{args.inspect}"
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.on_retry_foo(retry_count, delay, *args)
|
108
|
+
puts "!!on_retry_foo!! #{retry_count} #{delay} #{args.inspect}"
|
109
|
+
end
|
110
|
+
|
103
111
|
def self.foo(x)
|
104
112
|
$hooked_fail_count += 1
|
105
113
|
raise HookFailError, "Fail!" if $hooked_fail_count == 1
|
106
114
|
puts "This is the job running successfully!! #{x.inspect}"
|
107
115
|
end
|
108
|
-
end # HookedObjectSuccess
|
116
|
+
end # HookedObjectSuccess
|
data/test/fixtures/test_jobs.rb
CHANGED
@@ -27,7 +27,22 @@ class TestRetryJob
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
+
class TestConfigurableRetryJob
|
31
|
+
include Backburner::Queue
|
32
|
+
def self.perform(retry_count)
|
33
|
+
$worker_test_count += 1
|
34
|
+
raise RuntimeError unless $worker_test_count > retry_count
|
35
|
+
$worker_success = true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
30
39
|
class TestAsyncJob
|
31
40
|
include Backburner::Performable
|
32
41
|
def self.foo(x, y); $worker_test_count = x * y; end
|
33
|
-
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class TestLambdaQueueJob
|
45
|
+
include Backburner::Queue
|
46
|
+
queue lambda { |klass| klass.calculated_queue_name }
|
47
|
+
def self.calculated_queue_name; 'lambda-queue' end
|
48
|
+
end
|
data/test/helpers_test.rb
CHANGED
@@ -45,7 +45,7 @@ describe "Backburner::Helpers module" do
|
|
45
45
|
end # exception_message
|
46
46
|
|
47
47
|
describe "for queue_config" do
|
48
|
-
before { Backburner.expects(:configuration).returns(stub(:tube_namespace => "test.foo.job")) }
|
48
|
+
before { Backburner.expects(:configuration).returns(stub(:tube_namespace => "test.foo.job", :namespace_separator => '.')) }
|
49
49
|
|
50
50
|
it "accesses correct value for namespace" do
|
51
51
|
assert_equal "test.foo.job", queue_config.tube_namespace
|
@@ -53,7 +53,7 @@ describe "Backburner::Helpers module" do
|
|
53
53
|
end # config
|
54
54
|
|
55
55
|
describe "for expand_tube_name method" do
|
56
|
-
before { Backburner.stubs(:configuration).returns(stub(:tube_namespace => "test.foo.job.", :primary_queue => "backburner-jobs")) }
|
56
|
+
before { Backburner.stubs(:configuration).returns(stub(:tube_namespace => "test.foo.job.", :namespace_separator => '.', :primary_queue => "backburner-jobs")) }
|
57
57
|
|
58
58
|
it "supports base strings" do
|
59
59
|
assert_equal "test.foo.job.email/send-news", expand_tube_name("email/send_news")
|
@@ -75,8 +75,26 @@ describe "Backburner::Helpers module" do
|
|
75
75
|
it "supports class names" do
|
76
76
|
assert_equal "test.foo.job.backburner-jobs", expand_tube_name(RuntimeError)
|
77
77
|
end # class names
|
78
|
+
|
79
|
+
it "supports lambda in queue object" do
|
80
|
+
test = stub(:queue => lambda { |job_class| "email/send_news" })
|
81
|
+
assert_equal "test.foo.job.email/send-news", expand_tube_name(test)
|
82
|
+
end # lambdas in queue object
|
83
|
+
|
84
|
+
it "supports lambdas" do
|
85
|
+
test = lambda { "email/send_news" }
|
86
|
+
assert_equal "test.foo.job.email/send-news", expand_tube_name(test)
|
87
|
+
end #lambdas
|
78
88
|
end # expand_tube_name
|
79
89
|
|
90
|
+
describe "for alternative namespace separator" do
|
91
|
+
before { Backburner.stubs(:configuration).returns(stub(:tube_namespace => "test", :namespace_separator => '-', :primary_queue => "backburner-jobs")) }
|
92
|
+
|
93
|
+
it "uses alternative namespace separator" do
|
94
|
+
assert_equal "test-queue-name", expand_tube_name("queue_name")
|
95
|
+
end # simple string
|
96
|
+
end
|
97
|
+
|
80
98
|
describe "for resolve_priority method" do
|
81
99
|
before do
|
82
100
|
@original_queue_priority = Backburner.configuration.default_priority
|
data/test/hooks_test.rb
CHANGED
@@ -69,6 +69,20 @@ describe "Backburner::Hooks module" do
|
|
69
69
|
assert_match(/!!on_failure_foo!! RuntimeError \[10\]/, out)
|
70
70
|
end
|
71
71
|
end # on_failure
|
72
|
+
|
73
|
+
describe 'with on_retry' do
|
74
|
+
it "should support successful invocation" do
|
75
|
+
out = silenced { @hooks.invoke_hook_events(HookedObjectSuccess, :on_retry, 1, 0, 10) }
|
76
|
+
assert_match(/!!on_retry_foo!! 1 0 \[10\]/, out)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'with on_bury' do
|
81
|
+
it "should support successful invocation" do
|
82
|
+
out = silenced { @hooks.invoke_hook_events(HookedObjectSuccess, :on_bury, 10) }
|
83
|
+
assert_match(/!!on_bury_foo!! \[10\]/, out)
|
84
|
+
end
|
85
|
+
end
|
72
86
|
end # invoke_hook_events
|
73
87
|
|
74
88
|
describe "for around_hook_events method" do
|
data/test/performable_test.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require File.expand_path('../test_helper', __FILE__)
|
2
2
|
|
3
3
|
class TestObj
|
4
|
-
include Backburner::Performable
|
5
4
|
ID = 56
|
6
5
|
def id; ID; end
|
7
6
|
def self.find(id); TestObj.new if id == ID; end
|
@@ -9,30 +8,81 @@ class TestObj
|
|
9
8
|
def self.bar(state, state2); "baz #{state} #{state2}"; end
|
10
9
|
end
|
11
10
|
|
11
|
+
class PerformableTestObj < TestObj
|
12
|
+
include Backburner::Performable
|
13
|
+
end
|
14
|
+
|
15
|
+
class AutomagicTestObj < TestObj
|
16
|
+
# Don't include Backburner::Performable because it should be automagically included
|
17
|
+
def qux(state, state2); "garply #{state} #{state2}" end
|
18
|
+
def self.garply(state, state2); "thud #{state} #{state2}" end
|
19
|
+
def qux?; "garply!" end
|
20
|
+
end
|
21
|
+
|
22
|
+
class AsyncInstanceMethodsTestObj < PerformableTestObj; end
|
23
|
+
class AsyncStaticMethodsTestObj < PerformableTestObj; end
|
24
|
+
|
25
|
+
|
26
|
+
|
12
27
|
describe "Backburner::Performable module" do
|
13
28
|
after { ENV["TEST"] = nil }
|
14
29
|
|
15
30
|
describe "for async instance method" do
|
16
31
|
it "should invoke worker enqueue" do
|
17
|
-
Backburner::Worker.expects(:enqueue).with(
|
18
|
-
|
32
|
+
Backburner::Worker.expects(:enqueue).with(PerformableTestObj, [56, :foo, true, false], has_entries(:pri => 5000, :queue => "foo"))
|
33
|
+
PerformableTestObj.new.async(:pri => 5000, :queue => "foo").foo(true, false)
|
19
34
|
end
|
20
35
|
end # async instance
|
21
36
|
|
22
37
|
describe "for async class method" do
|
23
38
|
it "should invoke worker enqueue" do
|
24
|
-
Backburner::Worker.expects(:enqueue).with(
|
25
|
-
|
39
|
+
Backburner::Worker.expects(:enqueue).with(PerformableTestObj, [nil, :bar, true, false], has_entries(:pri => 5000, :queue => "foo"))
|
40
|
+
PerformableTestObj.async(:pri => 5000, :queue => "foo").bar(true, false)
|
26
41
|
end
|
27
42
|
end # async class
|
28
43
|
|
29
44
|
describe "for perform class method" do
|
30
45
|
it "should work for instance" do
|
31
|
-
assert_equal "bar true false",
|
46
|
+
assert_equal "bar true false", PerformableTestObj.perform(PerformableTestObj::ID, :foo, true, false)
|
32
47
|
end # instance
|
33
48
|
|
34
49
|
it "should work for class level" do
|
35
|
-
assert_equal "baz false true",
|
50
|
+
assert_equal "baz false true", PerformableTestObj.perform(nil, :bar, false, true)
|
36
51
|
end # class
|
37
52
|
end # perform
|
38
|
-
|
53
|
+
|
54
|
+
describe "for handle_asynchronously class method" do
|
55
|
+
it "should automagically asynchronously proxy calls to the method" do
|
56
|
+
Backburner::Performable.handle_asynchronously(AutomagicTestObj, :qux, :pri => 5000, :queue => "qux")
|
57
|
+
|
58
|
+
Backburner::Worker.expects(:enqueue).with(AutomagicTestObj, [56, :qux_without_async, true, false], has_entries(:pri => 5000, :queue => "qux"))
|
59
|
+
AutomagicTestObj.new.qux(true, false)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should work for class methods, too" do
|
63
|
+
Backburner::Performable.handle_static_asynchronously(AutomagicTestObj, :garply, :pri => 5000, :queue => "garply")
|
64
|
+
|
65
|
+
Backburner::Worker.expects(:enqueue).with(AutomagicTestObj, [nil, :garply_without_async, true, false], has_entries(:pri => 5000, :queue => "garply"))
|
66
|
+
AutomagicTestObj.garply(true, false)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should correctly handle punctuation" do
|
70
|
+
Backburner::Performable.handle_asynchronously(AutomagicTestObj, :qux?)
|
71
|
+
|
72
|
+
Backburner::Worker.expects(:enqueue).with(AutomagicTestObj, [56, :qux_without_async?], {})
|
73
|
+
AutomagicTestObj.new.qux?
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should be available for instance methods on any class that includes the Performable module" do
|
77
|
+
AsyncInstanceMethodsTestObj.handle_asynchronously :foo, pri: 5000, queue: 'qux'
|
78
|
+
Backburner::Worker.expects(:enqueue).with(AsyncInstanceMethodsTestObj, [56, :foo_without_async, true, false], has_entries(:pri => 5000, :queue => "qux"))
|
79
|
+
AsyncInstanceMethodsTestObj.new.foo(true, false)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should be available for class methods on any class that includes the Performable module" do
|
83
|
+
AsyncStaticMethodsTestObj.handle_static_asynchronously :bar, pri: 5000, queue: 'garply'
|
84
|
+
Backburner::Worker.expects(:enqueue).with(AsyncStaticMethodsTestObj, [nil, :bar_without_async, true, false], has_entries(:pri => 5000, :queue => "garply"))
|
85
|
+
AsyncStaticMethodsTestObj.bar(true, false)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/test/queue_test.rb
CHANGED
@@ -24,6 +24,11 @@ describe "Backburner::Queue module" do
|
|
24
24
|
NestedDemo::TestJobB.queue("nested/job")
|
25
25
|
assert_equal "nested/job", NestedDemo::TestJobB.queue
|
26
26
|
end
|
27
|
+
|
28
|
+
it "should allow lambdas" do
|
29
|
+
NestedDemo::TestJobB.queue(lambda { |klass| klass.name })
|
30
|
+
assert_equal "NestedDemo::TestJobB", NestedDemo::TestJobB.queue
|
31
|
+
end
|
27
32
|
end # queue
|
28
33
|
|
29
34
|
describe "for queue_priority assignment method" do
|
@@ -39,4 +44,4 @@ describe "Backburner::Queue module" do
|
|
39
44
|
assert_equal 300, NestedDemo::TestJobB.queue_respond_timeout
|
40
45
|
end
|
41
46
|
end # queue_respond_timeout
|
42
|
-
end # Backburner::Queue
|
47
|
+
end # Backburner::Queue
|
data/test/worker_test.rb
CHANGED
@@ -63,6 +63,14 @@ describe "Backburner::Worker module" do
|
|
63
63
|
assert_equal 100, job.ttr
|
64
64
|
assert_equal Backburner.configuration.default_priority, job.pri
|
65
65
|
end # async
|
66
|
+
|
67
|
+
it "should support enqueueing job with lambda queue" do
|
68
|
+
expected_queue_name = TestLambdaQueueJob.calculated_queue_name
|
69
|
+
Backburner::Worker.enqueue TestLambdaQueueJob, [6, 7], :queue => lambda { |klass| klass.calculated_queue_name }
|
70
|
+
job, body = pop_one_job(expected_queue_name)
|
71
|
+
assert_equal "TestLambdaQueueJob", body["class"]
|
72
|
+
assert_equal [6, 7], body["args"]
|
73
|
+
end
|
66
74
|
end # enqueue
|
67
75
|
|
68
76
|
describe "for start class method" do
|
@@ -117,4 +125,4 @@ describe "Backburner::Worker module" do
|
|
117
125
|
assert_equal ['baz', 'bam'], worker.tube_names
|
118
126
|
end
|
119
127
|
end # tube_names
|
120
|
-
end # Backburner::Worker
|
128
|
+
end # Backburner::Worker
|
@@ -54,7 +54,7 @@ describe "Backburner::Workers::Basic module" do
|
|
54
54
|
out = capture_stdout { worker.prepare }
|
55
55
|
assert_contains worker.tube_names, "demo.test.backburner-jobs"
|
56
56
|
assert_contains @worker_class.connection.tubes.watched.map(&:name), "demo.test.backburner-jobs"
|
57
|
-
assert_match(/demo\.test\.
|
57
|
+
assert_match(/demo\.test\.backburner-jobs/, out)
|
58
58
|
end # all read
|
59
59
|
end # prepare
|
60
60
|
|
@@ -172,6 +172,58 @@ describe "Backburner::Workers::Basic module" do
|
|
172
172
|
assert_equal true, $worker_success
|
173
173
|
end # retrying, succeeds
|
174
174
|
|
175
|
+
it "should back off retries exponentially" do
|
176
|
+
max_job_retries = 3
|
177
|
+
clear_jobs!('foo.bar')
|
178
|
+
Backburner.configure do |config|
|
179
|
+
config.max_job_retries = max_job_retries
|
180
|
+
config.retry_delay = 0
|
181
|
+
#config.retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) } # default retry_delay_proc
|
182
|
+
end
|
183
|
+
@worker_class.enqueue TestConfigurableRetryJob, [max_job_retries], :queue => 'foo.bar'
|
184
|
+
out = []
|
185
|
+
(max_job_retries + 1).times do
|
186
|
+
out << silenced(10) do
|
187
|
+
worker = @worker_class.new('foo.bar')
|
188
|
+
worker.prepare
|
189
|
+
worker.work_one_job
|
190
|
+
end
|
191
|
+
end
|
192
|
+
assert_match(/attempt 1 of 4, retrying in 0/, out.first)
|
193
|
+
assert_match(/attempt 2 of 4, retrying in 1/, out[1])
|
194
|
+
assert_match(/attempt 3 of 4, retrying in 8/, out[2])
|
195
|
+
assert_match(/Completed TestConfigurableRetryJob/m, out.last)
|
196
|
+
refute_match(/failed/, out.last)
|
197
|
+
assert_equal 4, $worker_test_count
|
198
|
+
assert_equal true, $worker_success
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should allow configurable back off retry delays" do
|
202
|
+
max_job_retries = 3
|
203
|
+
clear_jobs!('foo.bar')
|
204
|
+
Backburner.configure do |config|
|
205
|
+
config.max_job_retries = max_job_retries
|
206
|
+
config.retry_delay = 0
|
207
|
+
config.retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 2) }
|
208
|
+
end
|
209
|
+
@worker_class.enqueue TestConfigurableRetryJob, [max_job_retries], :queue => 'foo.bar'
|
210
|
+
out = []
|
211
|
+
(max_job_retries + 1).times do
|
212
|
+
out << silenced(5) do
|
213
|
+
worker = @worker_class.new('foo.bar')
|
214
|
+
worker.prepare
|
215
|
+
worker.work_one_job
|
216
|
+
end
|
217
|
+
end
|
218
|
+
assert_match(/attempt 1 of 4, retrying in 0/, out.first)
|
219
|
+
assert_match(/attempt 2 of 4, retrying in 1/, out[1])
|
220
|
+
assert_match(/attempt 3 of 4, retrying in 4/, out[2])
|
221
|
+
assert_match(/Completed TestConfigurableRetryJob/m, out.last)
|
222
|
+
refute_match(/failed/, out.last)
|
223
|
+
assert_equal 4, $worker_test_count
|
224
|
+
assert_equal true, $worker_success
|
225
|
+
end
|
226
|
+
|
175
227
|
it "should support event hooks without retry" do
|
176
228
|
$hooked_fail_count = 0
|
177
229
|
clear_jobs!('foo.bar.events')
|
@@ -188,6 +240,7 @@ describe "Backburner::Workers::Basic module" do
|
|
188
240
|
assert_match(/!!BEGIN around_perform_bar!! \[nil, "foo", 5\]/, out)
|
189
241
|
assert_match(/!!BEGIN around_perform_cat!! \[nil, "foo", 5\]/, out)
|
190
242
|
assert_match(/!!on_failure_foo!!.*HookFailError/, out)
|
243
|
+
assert_match(/!!on_bury_foo!! \[nil, "foo", 5\]/, out)
|
191
244
|
assert_match(/attempt 1 of 1, burying/, out)
|
192
245
|
end # event hooks, no retry
|
193
246
|
|
@@ -211,6 +264,7 @@ describe "Backburner::Workers::Basic module" do
|
|
211
264
|
assert_match(/!!BEGIN around_perform_cat!! \[nil, "foo", 5\]/, out)
|
212
265
|
assert_match(/!!on_failure_foo!!.*HookFailError/, out)
|
213
266
|
assert_match(/!!on_failure_foo!!.*retrying.*around_perform_bar.*around_perform_cat/m, out)
|
267
|
+
assert_match(/!!on_retry_foo!! 1 0 \[nil, "foo", 5\]/, out)
|
214
268
|
assert_match(/attempt 1 of 2, retrying/, out)
|
215
269
|
assert_match(/!!before_perform_foo!! \[nil, "foo", 5\]/, out)
|
216
270
|
assert_match(/!!END around_perform_bar!! \[nil, "foo", 5\]/, out)
|
@@ -246,7 +300,11 @@ describe "Backburner::Workers::Basic module" do
|
|
246
300
|
end # stopping perform
|
247
301
|
|
248
302
|
after do
|
249
|
-
Backburner.configure
|
303
|
+
Backburner.configure do |config|
|
304
|
+
config.max_job_retries = 0
|
305
|
+
config.retry_delay = 5
|
306
|
+
config.retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) }
|
307
|
+
end
|
250
308
|
end
|
251
309
|
end # work_one_job
|
252
310
|
end # Worker
|
metadata
CHANGED
@@ -1,55 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backburner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Esquenazi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: beaneater
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: dante
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">"
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: 0.1.5
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 0.1.5
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
@@ -70,14 +70,14 @@ dependencies:
|
|
70
70
|
name: mocha
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- -
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
75
|
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- -
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
description: Beanstalk background job processing made easy
|
@@ -88,9 +88,9 @@ executables:
|
|
88
88
|
extensions: []
|
89
89
|
extra_rdoc_files: []
|
90
90
|
files:
|
91
|
-
- .DS_Store
|
92
|
-
- .gitignore
|
93
|
-
- .travis.yml
|
91
|
+
- ".DS_Store"
|
92
|
+
- ".gitignore"
|
93
|
+
- ".travis.yml"
|
94
94
|
- CHANGELOG.md
|
95
95
|
- CONTRIBUTING.md
|
96
96
|
- Gemfile
|
@@ -156,17 +156,17 @@ require_paths:
|
|
156
156
|
- lib
|
157
157
|
required_ruby_version: !ruby/object:Gem::Requirement
|
158
158
|
requirements:
|
159
|
-
- -
|
159
|
+
- - ">="
|
160
160
|
- !ruby/object:Gem::Version
|
161
161
|
version: '0'
|
162
162
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
163
|
requirements:
|
164
|
-
- -
|
164
|
+
- - ">="
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
167
|
requirements: []
|
168
168
|
rubyforge_project:
|
169
|
-
rubygems_version: 2.
|
169
|
+
rubygems_version: 2.2.2
|
170
170
|
signing_key:
|
171
171
|
specification_version: 4
|
172
172
|
summary: Reliable beanstalk background job processing made easy for Ruby and Sinatra
|