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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 89f6987a5f81f904f8e2dd8d48bad8deea664a7e
4
- data.tar.gz: d0a1cdf8c5cd0087f60d4c29da334f40ebc00a3f
3
+ metadata.gz: 9f2543596fd5e337e0b4514ca94f8b32959c3901
4
+ data.tar.gz: eb4133d3c373307e9399636ce8c5c4df7630e74a
5
5
  SHA512:
6
- metadata.gz: 19fc5c5d7086baf5ca15cc8cf93d0a429cd858ac30fbdfe86719322bd8704edacfb008d3568eae6f5c6c1a029e7831a0ef6052345cb934d7fea45cca90e60103
7
- data.tar.gz: 3da11501d42cecb97fae3428cdf9d3f9e7403f0eab8801e581ff90685a79646a12f4ee7d95811efd5b4339995f97ff96bca7d13fe8ca8372b99b52047d28a2b2
6
+ metadata.gz: 58857e61ce26ff416fa6b3732839adedc2b9230886cff2fafa812c444598b264525f141a48e9eb2294ff3d8b55bd105fb00c28a89fdf1eb7250ca74022a41567
7
+ data.tar.gz: dcbde0b50cc908dbb6052769a546c72589585a86b9d50ec5a4ceea1c6bdc9977430cfa45321593de52bb0e2f9048dd78b151f41de524be1b730b94bc64765606
@@ -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 = ["beanstalk://127.0.0.1", "..."]
92
- config.tube_namespace = "some.app.production"
93
- config.on_error = lambda { |e| puts e }
94
- config.max_job_retries = 3 # default 0 retries
95
- config.retry_delay = 2 # default 5 seconds
96
- config.default_priority = 65536
97
- config.respond_timeout = 120
98
- config.default_worker = Backburner::Workers::Simple
99
- config.logger = Logger.new(STDOUT)
100
- config.primary_queue = "backburner-jobs"
101
- config.priority_labels = { :custom => 50, :useless => 1000 }
102
- config.reserve_timeout = nil
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 | Description |
109
- | ----------------- | ------------------------------- |
110
- | `beanstalk_url` | Address such as 'beanstalk://127.0.0.1' or an array of addresses. |
111
- | `tube_namespace` | Prefix used for all tubes related to this backburner queue. |
112
- | `on_error` | Lambda invoked with the error whenever any job in the system fails. |
113
- | `default_worker` | Worker class that will be used if no other worker is specified. |
114
- | `max_job_retries` | Integer defines how many times to retry a job before burying. |
115
- | `retry_delay` | Integer defines the base time to wait (in secs) between job retries. |
116
- | `logger` | Logger recorded to when backburner wants to report info or errors. |
117
- | `primary_queue` | Primary queue used for a job when an alternate queue is not given. |
118
- | `priority_labels` | Hash of named priority definitions for your app. |
119
- | `reserve_timeout` | Duration to wait for work from a single server, or nil for forever. |
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
- The queue name used by default is `{namespace}.backburner-jobs` if not otherwise specified.
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 (with progressive delays in between) until the `max_job_retries` configuration is reached.
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
@@ -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
- load_enviroment(opts[:require])
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.load_enviroment(file = nil)
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 defined?(::Padrino)
41
- # Padrino
42
- require "padrino"
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 # beanstalk url connection
6
- attr_accessor :tube_namespace # namespace prefix for every queue
7
- attr_accessor :default_priority # default job priority
8
- attr_accessor :respond_timeout # default job timeout
9
- attr_accessor :on_error # error handler
10
- attr_accessor :max_job_retries # max job retries
11
- attr_accessor :retry_delay # retry delay in seconds
12
- attr_accessor :default_queues # default queues
13
- attr_accessor :logger # logger
14
- attr_accessor :default_worker # default worker class
15
- attr_accessor :primary_queue # the general queue
16
- attr_accessor :priority_labels # priority labels
17
- attr_accessor :reserve_timeout # duration to wait to reserve on a single server
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 = "beanstalk://localhost"
21
- @tube_namespace = "backburner.worker.queue"
22
- @default_priority = 65536
23
- @respond_timeout = 120
24
- @on_error = nil
25
- @max_job_retries = 0
26
- @retry_delay = 5
27
- @default_queues = []
28
- @logger = nil
29
- @default_worker = Backburner::Workers::Simple
30
- @primary_queue = "backburner-jobs"
31
- @priority_labels = PRIORITY_LABELS
32
- @reserve_timeout = nil
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
@@ -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(".").gsub(/\.+/, '.').split(':').first
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
@@ -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 - 1) { job_class.perform(*args) }
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
@@ -22,7 +22,7 @@ module Backburner
22
22
  if name
23
23
  @queue_name = name
24
24
  else # accessor
25
- @queue_name || Backburner.configuration.primary_queue
25
+ (@queue_name.is_a?(Proc) ? @queue_name.call(self) : @queue_name) || Backburner.configuration.primary_queue
26
26
  end
27
27
  end
28
28
 
@@ -1,3 +1,3 @@
1
1
  module Backburner
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -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
- tube = connection.tubes[expand_tube_name(opts[:queue] || job_class)]
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 + num_retries ** 3
143
- job.release(:delay => delay)
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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(TestObj, [56, :foo, true, false], has_entries(:pri => 5000, :queue => "foo"))
18
- TestObj.new.async(:pri => 5000, :queue => "foo").foo(true, false)
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(TestObj, [nil, :bar, true, false], has_entries(:pri => 5000, :queue => "foo"))
25
- TestObj.async(:pri => 5000, :queue => "foo").bar(true, false)
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", TestObj.perform(TestObj::ID, :foo, 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", TestObj.perform(nil, :bar, false, true)
50
+ assert_equal "baz false true", PerformableTestObj.perform(nil, :bar, false, true)
36
51
  end # class
37
52
  end # perform
38
- end
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
@@ -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
@@ -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\.test-job/, out)
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 { |config| config.max_job_retries = 0; config.retry_delay = 5 }
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.0.0
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-04-26 00:00:00.000000000 Z
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.0.6
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