backburner 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|