backburner 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,17 @@
1
1
  # CHANGELOG
2
2
 
3
- ## Released 0.1.1 (Nov 6th 2012)
3
+ ## Version 0.1.3 (Unreleased)
4
+
5
+ ## Version 0.1.2 (Nov 7th 2012)
6
+
7
+ * Adds ability to specify a custom logger.
8
+ * Adds job retry configuration and worker support.
9
+
10
+ ## Version 0.1.1 (Nov 6th 2012)
4
11
 
5
12
  * Fix issue with timed out reserves
6
13
 
7
- ## Released 0.1.0 (Nov 4th 2012)
14
+ ## Version 0.1.0 (Nov 4th 2012)
8
15
 
9
16
  * Switch to beaneater as new ruby beanstalkd client
10
17
  * Add support for array of connections in `beanstalk_url`
data/README.md CHANGED
@@ -86,14 +86,20 @@ Backburner.configure do |config|
86
86
  config.beanstalk_url = ["beanstalk://127.0.0.1", "beanstalk://127.0.0.1:11301"]
87
87
  config.tube_namespace = "some.app.production"
88
88
  config.on_error = lambda { |e| puts e }
89
+ config.max_job_retries = 3 # default 0 retries
90
+ config.retry_delay = 2 # default 5 seconds
89
91
  config.default_priority = 65536
90
- config.respond_timeout = 120
92
+ config.respond_timeout = 120
93
+ config.logger = Logger.new(STDOUT)
91
94
  end
92
95
  ```
93
96
 
94
- The `beanstalk_url` supports a string such as 'beanstalk://127.0.0.1' or an array of addresses.
95
- The `tube_namespace` is the prefix used for all tubes related to this backburner queue.
96
- The `on_error` is a callback that gets invoked with the error whenever a job fails
97
+ * The `beanstalk_url` supports a string such as 'beanstalk://127.0.0.1' or an array of addresses.
98
+ * The `tube_namespace` is the prefix used for all tubes related to this backburner queue.
99
+ * The `on_error` is a callback that gets invoked with the error whenever a job fails.
100
+ * The `max_job_retries` determines how many times to retry a job before burying
101
+ * The `retry_delay` determines the base time to wait (in secs) between retries
102
+ * The `logger` is the logger object written to when backburner wants to report info or errors.
97
103
 
98
104
  ## Usage
99
105
 
@@ -227,7 +233,20 @@ The `default_queues` stores the specific list of queues that should be processed
227
233
 
228
234
  ### Failures
229
235
 
230
- You can setup the error handler for jobs using configure:
236
+ When a job fails in backburner (usually because an exception was raised), the job will be released
237
+ and retried again (with progressive delays in between) until the `max_job_retries` configuration is reached.
238
+
239
+ ```ruby
240
+ Backburner.configure do |config|
241
+ config.max_job_retries = 3 # retry jobs 3 times
242
+ config.retry_delay = 2 # wait 2 seconds in between retries
243
+ end
244
+ ```
245
+
246
+ Note the default `max_job_retries` is 0, meaning that by default **jobs are not retried**.
247
+ If continued retry attempts fail, the job will be buried and can be 'kicked' later for inspection.
248
+
249
+ You can also setup a custom error handler for jobs using configure:
231
250
 
232
251
  ```ruby
233
252
  Backburner.configure do |config|
@@ -235,12 +254,23 @@ Backburner.configure do |config|
235
254
  end
236
255
  ```
237
256
 
238
- Now all beanstalk queue errors will show up on airbrake.
239
- If a job fails in beanstalk, the job is automatically buried and must be 'kicked' later.
257
+ Now all backburner queue errors will appear on airbrake for deeper inspection.
240
258
 
241
259
  ### Logging
242
260
 
243
- Right now, all logging happens to standard out and can be piped to a file or any other output manually. More on logging coming later.
261
+ Logging in backburner is rather simple. When a job is run, the log records that. When a job
262
+ fails, the log records that. When any exceptions occur during processing, the log records that.
263
+
264
+ By default, the log will print to standard out. You can customize the log to output to any
265
+ standard logger by controlling the configuration option:
266
+
267
+ ```ruby
268
+ Backburner.configure do |config|
269
+ config.logger = Logger.new(STDOUT)
270
+ end
271
+ ```
272
+
273
+ Be sure to check logs whenever things do not seem to be processing.
244
274
 
245
275
  ### Web Front-end
246
276
 
data/TODO CHANGED
@@ -1,9 +1,6 @@
1
- - Front-end in sinatra for viewing beanstalk jobs
2
- - https://github.com/denniskuczynski/beanstalkd_view
3
- - Better logging
4
- - https://github.com/dpree/stalker/commit/70e94b7b445e08166bf2288dd1b7853396b26a86
5
- - Better error handling (failures, retries)
6
- - https://github.com/michaeldwan/stalker/commit/afd7e94d20991bcd96b65eda7c979d92d8746c3b
7
- - https://github.com/michaeldwan/stalker/blob/master/lib/stalker/worker.rb
1
+ - Custom front-end in sinatra for viewing beanstalk jobs
2
+ - Refer to https://github.com/denniskuczynski/beanstalkd_view
8
3
  - Fork jobs to control memory
4
+ - https://github.com/michaeldwan/stalker/commit/386267690a7c03e11d1a8b7b6f08b7c9c7cd2c0d
5
+ - Add hooks
9
6
  - https://github.com/michaeldwan/stalker/commit/386267690a7c03e11d1a8b7b6f08b7c9c7cd2c0d
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
15
15
  s.require_paths = ["lib"]
16
16
  s.version = Backburner::VERSION
17
17
 
18
- s.add_runtime_dependency 'beaneater', '~> 0.1.1'
18
+ s.add_runtime_dependency 'beaneater', '~> 0.1.2'
19
19
  s.add_runtime_dependency 'dante', '~> 0.1.5'
20
20
 
21
21
  s.add_development_dependency 'rake'
@@ -0,0 +1,31 @@
1
+ $:.unshift "lib"
2
+ require 'backburner'
3
+
4
+ $error = 0
5
+
6
+ class User
7
+ include Backburner::Performable
8
+ attr_accessor :id, :name
9
+
10
+ def self.foo(x, y)
11
+ $error += 1
12
+ raise "fail #{$error}" unless $error > 3
13
+ puts "User #foo args [#{x}, #{y}] Success!!"
14
+ end
15
+ end
16
+
17
+ # Configure Backburner
18
+ Backburner.configure do |config|
19
+ config.beanstalk_url = "beanstalk://127.0.0.1"
20
+ config.tube_namespace = "demo.production"
21
+ config.on_error = lambda { |e| puts "HEY!!! #{e.class}" }
22
+ config.max_job_retries = 3
23
+ config.retry_delay = 0
24
+ end
25
+
26
+ # Enqueue tasks
27
+ User.async(:queue => "retried").foo("bar", "baz")
28
+
29
+ # Run work
30
+ # Backburner.default_queues << "user"
31
+ Backburner.work
@@ -5,7 +5,10 @@ module Backburner
5
5
  attr_accessor :default_priority # default job priority
6
6
  attr_accessor :respond_timeout # default job timeout
7
7
  attr_accessor :on_error # error handler
8
+ attr_accessor :max_job_retries # max job retries
9
+ attr_accessor :retry_delay # retry delay in seconds
8
10
  attr_accessor :default_queues # default queues
11
+ attr_accessor :logger # logger
9
12
 
10
13
  def initialize
11
14
  @beanstalk_url = "beanstalk://localhost"
@@ -13,7 +16,10 @@ module Backburner
13
16
  @default_priority = 65536
14
17
  @respond_timeout = 120
15
18
  @on_error = nil
19
+ @max_job_retries = 0
20
+ @retry_delay = 5
16
21
  @default_queues = []
22
+ @logger = nil
17
23
  end
18
24
  end # Configuration
19
25
  end # Backburner
@@ -13,7 +13,7 @@ module Backburner
13
13
  connect!
14
14
  end
15
15
 
16
- # Sets the delegator object to the underlying beanstalk connection
16
+ # Sets the delegator object to the underlying beaneater pool
17
17
  # self.put(...)
18
18
  def __getobj__
19
19
  __setobj__(@beanstalk)
@@ -68,13 +68,13 @@ module Backburner
68
68
  constant
69
69
  end
70
70
 
71
- # Returns tube_namespace for backburner
71
+ # Returns configuration options for backburner
72
72
  #
73
73
  # @example
74
- # tube_namespace => "some.namespace"
74
+ # config.max_job_retries => 3
75
75
  #
76
- def tube_namespace
77
- Backburner.configuration.tube_namespace
76
+ def config
77
+ Backburner.configuration
78
78
  end
79
79
 
80
80
  # Expands a tube to include the prefix
@@ -84,7 +84,7 @@ module Backburner
84
84
  # expand_tube_name(FooJob) # => <prefix>.foo-job
85
85
  #
86
86
  def expand_tube_name(tube)
87
- prefix = tube_namespace
87
+ prefix = config.tube_namespace
88
88
  queue_name = if tube.is_a?(String)
89
89
  tube
90
90
  elsif tube.respond_to?(:queue) # use queue name
@@ -94,7 +94,7 @@ module Backburner
94
94
  else # turn into a string
95
95
  tube.to_s
96
96
  end
97
- [prefix.gsub(/\.$/, ''), dasherize(queue_name).gsub(/^#{prefix}/, '')].join(".")
97
+ [prefix.gsub(/\.$/, ''), dasherize(queue_name).gsub(/^#{prefix}/, '')].join(".").gsub(/\.+/, '.')
98
98
  end
99
99
 
100
100
  end
@@ -1,11 +1,12 @@
1
1
  module Backburner
2
2
  # A single backburner job which can be processed and removed by the worker
3
- class Job
3
+ class Job < SimpleDelegator
4
4
  include Backburner::Helpers
5
5
 
6
6
  # Raises when a job times out
7
7
  class JobTimeout < RuntimeError; end
8
8
  class JobNotFound < RuntimeError; end
9
+ class JobFormatInvalid < RuntimeError; end
9
10
 
10
11
  attr_accessor :task, :body, :name, :args
11
12
 
@@ -21,6 +22,16 @@ module Backburner
21
22
  @task = task
22
23
  @body = task.body.is_a?(Hash) ? task.body : JSON.parse(task.body)
23
24
  @name, @args = body["class"], body["args"]
25
+ rescue => ex # Job was not valid format
26
+ self.bury
27
+ raise JobFormatInvalid, "Job body could not be parsed: #{ex.inspect}"
28
+ end
29
+
30
+ # Sets the delegator object to the underlying beaneater job
31
+ # self.bury
32
+ def __getobj__
33
+ __setobj__(@task)
34
+ super
24
35
  end
25
36
 
26
37
  # Processes a job and handles any failure, deleting the job once complete
@@ -33,11 +44,6 @@ module Backburner
33
44
  task.delete
34
45
  end
35
46
 
36
- # Bury a job out of the active queue if that job fails
37
- def bury
38
- task.bury
39
- end
40
-
41
47
  protected
42
48
 
43
49
  # Returns the class for the job handler
@@ -1,3 +1,5 @@
1
+ require 'logger'
2
+
1
3
  module Backburner
2
4
  module Logger
3
5
  # Loads in instance and class levels
@@ -6,16 +8,21 @@ module Backburner
6
8
  end
7
9
 
8
10
  # Print out when a job is about to begin
9
- def log_job_begin(body)
10
- log_info [ "Working", body ].join(' ')
11
- @job_begun = Time.now
11
+ def log_job_begin(name, args)
12
+ log_info "Work job #{name} with #{args.inspect}"
13
+ @job_started_at = Time.now
12
14
  end
13
15
 
14
16
  # Print out when a job completed
15
- def log_job_end(name, failed=false)
16
- ellapsed = Time.now - @job_begun
17
+ def log_job_end(name, message = nil)
18
+ ellapsed = Time.now - job_started_at
17
19
  ms = (ellapsed.to_f * 1000).to_i
18
- log_info "Finished #{name} in #{ms}ms #{failed ? ' (failed)' : ''}"
20
+ log_info("Finished #{name} in #{ms}ms #{message}")
21
+ end
22
+
23
+ # Returns true if the job logging started
24
+ def job_started_at
25
+ @job_started_at
19
26
  end
20
27
 
21
28
  # Print a message to stdout
@@ -24,7 +31,7 @@ module Backburner
24
31
  # log_info("Working on task")
25
32
  #
26
33
  def log_info(msg)
27
- puts msg
34
+ logger ? logger.info(msg) : puts(msg)
28
35
  end
29
36
 
30
37
  # Print an error to stderr
@@ -33,8 +40,12 @@ module Backburner
33
40
  # log_error("Task failed!")
34
41
  #
35
42
  def log_error(msg)
36
- $stderr.puts msg
43
+ logger ? logger.error(msg) : $stderr.puts(msg)
37
44
  end
38
45
 
46
+ # Return logger if specified
47
+ def logger
48
+ Backburner.configuration.logger
49
+ end
39
50
  end
40
51
  end
@@ -1,3 +1,3 @@
1
1
  module Backburner
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
@@ -93,16 +93,24 @@ module Backburner
93
93
  #
94
94
  def work_one_job
95
95
  job = Backburner::Job.new(self.connection.tubes.reserve)
96
- self.class.log_job_begin(job.body)
96
+ self.log_job_begin(job.name, job.args)
97
97
  job.process
98
- self.class.log_job_end(job.name)
99
- rescue => e
100
- self.class.log_error self.class.exception_message(e)
101
- if job # bury failed job
98
+ self.log_job_end(job.name)
99
+ rescue Backburner::Job::JobFormatInvalid => e
100
+ self.log_error self.exception_message(e)
101
+ rescue => e # Error occurred processing job
102
+ self.log_error self.exception_message(e)
103
+ num_retries = job.stats.releases
104
+ retry_status = "failed: attempt #{num_retries+1} of #{config.max_job_retries+1}"
105
+ if num_retries < config.max_job_retries # retry again
106
+ delay = config.retry_delay + num_retries ** 3
107
+ job.release(:delay => delay)
108
+ self.log_job_end(job.name, "#{retry_status}, retrying in #{delay}s") if job_started_at
109
+ else # retries failed, bury
102
110
  job.bury
103
- self.class.log_job_end(job.name, 'failed') if @job_begun
104
- handle_error(e, job.name, job.args)
111
+ self.log_job_end(job.name, "#{retry_status}, burying") if job_started_at
105
112
  end
113
+ handle_error(e, job.name, job.args)
106
114
  end
107
115
 
108
116
  protected
@@ -111,7 +119,7 @@ module Backburner
111
119
  # Filtered for tubes that match the known prefix
112
120
  def all_existing_queues
113
121
  known_queues = Backburner::Worker.known_queue_classes.map(&:queue)
114
- existing_tubes = self.connection.tubes.all.map(&:name).select { |tube| tube =~ /^#{tube_namespace}/ }
122
+ existing_tubes = self.connection.tubes.all.map(&:name).select { |tube| tube =~ /^#{config.tube_namespace}/ }
115
123
  known_queues + existing_tubes
116
124
  end
117
125
 
@@ -44,13 +44,13 @@ describe "Backburner::Helpers module" do
44
44
  end
45
45
  end # exception_message
46
46
 
47
- describe "for tube_namespace" do
47
+ describe "for config" do
48
48
  before { Backburner.expects(:configuration).returns(stub(:tube_namespace => "test.foo.job")) }
49
49
 
50
- it "accesses correct value" do
51
- assert_equal "test.foo.job", tube_namespace
50
+ it "accesses correct value for namespace" do
51
+ assert_equal "test.foo.job", config.tube_namespace
52
52
  end
53
- end # tube_namespace
53
+ end # config
54
54
 
55
55
  describe "for expand_tube_name method" do
56
56
  before { Backburner.expects(:configuration).returns(stub(:tube_namespace => "test.foo.job.")) }
@@ -13,35 +13,50 @@ module NestedDemo
13
13
  end
14
14
 
15
15
  describe "Backburner::Job module" do
16
- describe "for initialize method with hash" do
17
- before do
18
- @task_body = { "class" => "NewsletterSender", "args" => ["foo@bar.com", "bar@foo.com"] }
19
- @task = stub(:body => @task_body, :ttr => 120, :delete => true, :bury => true)
20
- end
16
+ describe "for initialize" do
17
+ describe "with hash" do
18
+ before do
19
+ @task_body = { "class" => "NewsletterSender", "args" => ["foo@bar.com", "bar@foo.com"] }
20
+ @task = stub(:body => @task_body, :ttr => 120, :delete => true, :bury => true)
21
+ end
21
22
 
22
- it "should create job with correct task data" do
23
- @job = Backburner::Job.new(@task)
24
- assert_equal @task, @job.task
25
- assert_equal ["class", "args"], @job.body.keys
26
- assert_equal @task_body["class"], @job.name
27
- assert_equal @task_body["args"], @job.args
28
- end
29
- end # initialize with json
23
+ it "should create job with correct task data" do
24
+ @job = Backburner::Job.new(@task)
25
+ assert_equal @task, @job.task
26
+ assert_equal ["class", "args"], @job.body.keys
27
+ assert_equal @task_body["class"], @job.name
28
+ assert_equal @task_body["args"], @job.args
29
+ end
30
+ end # with hash
30
31
 
31
- describe "for initialize method with json string" do
32
- before do
33
- @task_body = { "class" => "NewsletterSender", "args" => ["foo@bar.com", "bar@foo.com"] }
34
- @task = stub(:body => @task_body.to_json, :ttr => 120, :delete => true, :bury => true)
35
- end
32
+ describe "with json string" do
33
+ before do
34
+ @task_body = { "class" => "NewsletterSender", "args" => ["foo@bar.com", "bar@foo.com"] }
35
+ @task = stub(:body => @task_body.to_json, :ttr => 120, :delete => true, :bury => true)
36
+ end
36
37
 
37
- it "should create job with correct task data" do
38
- @job = Backburner::Job.new(@task)
39
- assert_equal @task, @job.task
40
- assert_equal ["class", "args"], @job.body.keys
41
- assert_equal @task_body["class"], @job.name
42
- assert_equal @task_body["args"], @job.args
43
- end
44
- end # initialize with json
38
+ it "should create job with correct task data" do
39
+ @job = Backburner::Job.new(@task)
40
+ assert_equal @task, @job.task
41
+ assert_equal ["class", "args"], @job.body.keys
42
+ assert_equal @task_body["class"], @job.name
43
+ assert_equal @task_body["args"], @job.args
44
+ end
45
+ end # with json
46
+
47
+ describe "with invalid string" do
48
+ before do
49
+ @task_body = "^%$*&^*"
50
+ @task = stub(:body => @task_body, :ttr => 120, :delete => true, :bury => true)
51
+ end
52
+
53
+ it "should raise a job format exception" do
54
+ assert_raises(Backburner::Job::JobFormatInvalid) {
55
+ @job = Backburner::Job.new(@task)
56
+ }
57
+ end
58
+ end # invalid
59
+ end # initialize
45
60
 
46
61
  describe "for process method" do
47
62
  describe "with valid task" do
@@ -85,7 +100,7 @@ describe "Backburner::Job module" do
85
100
  end # invalid
86
101
  end # process
87
102
 
88
- describe "for bury method" do
103
+ describe "for simple delegation method" do
89
104
  before do
90
105
  @task_body = { "class" => "NestedDemo::TestJobC", "args" => [56] }
91
106
  @task = stub(:body => @task_body, :ttr => 120, :delete => true, :bury => true)
@@ -96,5 +111,5 @@ describe "Backburner::Job module" do
96
111
  @job = Backburner::Job.new(@task)
97
112
  @job.bury
98
113
  end # bury
99
- end # bury
114
+ end # simple delegation
100
115
  end
@@ -3,11 +3,26 @@ require File.expand_path('../test_helper', __FILE__)
3
3
  describe "Backburner::Logger module" do
4
4
  include Backburner::Logger
5
5
 
6
+ before do
7
+ @strio = StringIO.new
8
+ @logger = Logger.new(@strio)
9
+ end
10
+
6
11
  describe "for log_info method" do
7
12
  it "prints out to std out" do
8
13
  output = capture_stdout { log_info("foo") }
9
14
  assert_equal "foo\n", output
10
15
  end
16
+
17
+ it "can be configured to log to logger" do
18
+ Backburner.configure { |config| config.logger = @logger }
19
+ log_info("foo")
20
+ assert_match /I,.*?foo/, @strio.string
21
+ end
22
+
23
+ after do
24
+ Backburner.configure { |config| config.logger = nil }
25
+ end
11
26
  end # log_info
12
27
 
13
28
  describe "for log_error method" do
@@ -15,5 +30,15 @@ describe "Backburner::Logger module" do
15
30
  output = capture_stdout { log_error("bar") }
16
31
  assert_equal "bar\n", output
17
32
  end
33
+
34
+ it "can be configured to log to logger" do
35
+ Backburner.configure { |config| config.logger = @logger }
36
+ log_error("bar")
37
+ assert_match /E,.*?bar/, @strio.string
38
+ end
39
+
40
+ after do
41
+ Backburner.configure { |config| config.logger = nil }
42
+ end
18
43
  end # log_error
19
44
  end
@@ -90,4 +90,12 @@ class MiniTest::Spec
90
90
  silenced(3) { @res = connection.tubes.reserve }
91
91
  return @res, @res.body
92
92
  end
93
+
94
+ # clear_jobs!('foo')
95
+ def clear_jobs!(*tube_names)
96
+ tube_names.each do |tube_name|
97
+ expanded_name = [Backburner.configuration.tube_namespace, tube_name].join(".")
98
+ Backburner::Worker.connection.tubes.find(expanded_name).clear
99
+ end
100
+ end
93
101
  end # MiniTest::Spec
@@ -1,6 +1,7 @@
1
1
  require File.expand_path('../test_helper', __FILE__)
2
2
 
3
3
  $worker_test_count = 0
4
+ $worker_success = false
4
5
 
5
6
  class TestJob
6
7
  include Backburner::Queue
@@ -13,13 +14,24 @@ class TestFailJob
13
14
  def self.perform(x, y); raise RuntimeError; end
14
15
  end
15
16
 
17
+ class TestRetryJob
18
+ include Backburner::Queue
19
+ def self.perform(x, y)
20
+ $worker_test_count += 1
21
+ raise RuntimeError unless $worker_test_count > 2
22
+ $worker_success = true
23
+ end
24
+ end
25
+
16
26
  class TestAsyncJob
17
27
  include Backburner::Performable
18
28
  def self.foo(x, y); $worker_test_count = x * y; end
19
29
  end
20
30
 
21
31
  describe "Backburner::Worker module" do
22
- before { Backburner.default_queues.clear }
32
+ before do
33
+ Backburner.default_queues.clear
34
+ end
23
35
 
24
36
  describe "for enqueue class method" do
25
37
  it "should support enqueuing job" do
@@ -144,8 +156,13 @@ describe "Backburner::Worker module" do
144
156
  end # prepare
145
157
 
146
158
  describe "for work_one_job method" do
147
- it "should work an enqueued job" do
159
+ before do
148
160
  $worker_test_count = 0
161
+ $worker_success = false
162
+ end
163
+
164
+ it "should work an enqueued job" do
165
+ clear_jobs!("foo.bar")
149
166
  Backburner::Worker.enqueue TestJob, [1, 2], :queue => "foo.bar"
150
167
  silenced(2) do
151
168
  worker = Backburner::Worker.new('foo.bar')
@@ -155,28 +172,94 @@ describe "Backburner::Worker module" do
155
172
  assert_equal 3, $worker_test_count
156
173
  end # enqueue
157
174
 
175
+ it "should fail quietly if there's an argument error" do
176
+ clear_jobs!("foo.bar")
177
+ Backburner::Worker.enqueue TestJob, ["bam", "foo", "bar"], :queue => "foo.bar"
178
+ out = silenced(2) do
179
+ worker = Backburner::Worker.new('foo.bar')
180
+ worker.prepare
181
+ worker.work_one_job
182
+ end
183
+ assert_match(/Exception ArgumentError/, out)
184
+ assert_equal 0, $worker_test_count
185
+ end # fail, argument
186
+
158
187
  it "should work an enqueued failing job" do
159
- $worker_test_count = 0
160
- Backburner::Worker.enqueue TestFailJob, [1, 2], :queue => "foo.bar.fail"
188
+ clear_jobs!("foo.bar")
189
+ Backburner::Worker.enqueue TestFailJob, [1, 2], :queue => "foo.bar"
161
190
  Backburner::Job.any_instance.expects(:bury).once
162
191
  out = silenced(2) do
163
- worker = Backburner::Worker.new('foo.bar.fail')
192
+ worker = Backburner::Worker.new('foo.bar')
164
193
  worker.prepare
165
194
  worker.work_one_job
166
195
  end
167
196
  assert_match(/Exception RuntimeError/, out)
168
197
  assert_equal 0, $worker_test_count
169
- end # fail
198
+ end # fail, runtime error
199
+
200
+ it "should work an invalid job parsed" do
201
+ Beaneater::Tubes.any_instance.expects(:reserve).returns(stub(:body => "{%$^}", :bury => true))
202
+ out = silenced(2) do
203
+ worker = Backburner::Worker.new('foo.bar')
204
+ worker.prepare
205
+ worker.work_one_job
206
+ end
207
+ assert_match(/Exception Backburner::Job::JobFormatInvalid/, out)
208
+ assert_equal 0, $worker_test_count
209
+ end # fail, runtime error
170
210
 
171
211
  it "should work for an async job" do
172
- $worker_test_count = 0
173
- TestAsyncJob.async(:queue => "bar.baz").foo(3, 5)
212
+ clear_jobs!('foo.bar')
213
+ TestAsyncJob.async(:queue => 'foo.bar').foo(3, 5)
174
214
  silenced(2) do
175
- worker = Backburner::Worker.new('bar.baz')
215
+ worker = Backburner::Worker.new('foo.bar')
176
216
  worker.prepare
177
217
  worker.work_one_job
178
218
  end
179
219
  assert_equal 15, $worker_test_count
180
220
  end # async
221
+
222
+ it "should support retrying jobs and burying" do
223
+ clear_jobs!('foo.bar')
224
+ Backburner.configure { |config| config.max_job_retries = 1; config.retry_delay = 0 }
225
+ Backburner::Worker.enqueue TestRetryJob, ["bam", "foo"], :queue => 'foo.bar'
226
+ out = []
227
+ 2.times do
228
+ out << silenced(2) do
229
+ worker = Backburner::Worker.new('foo.bar')
230
+ worker.prepare
231
+ worker.work_one_job
232
+ end
233
+ end
234
+ assert_match /attempt 1 of 2, retrying/, out.first
235
+ assert_match /Finished TestRetryJob/m, out.last
236
+ assert_match /attempt 2 of 2, burying/m, out.last
237
+ assert_equal 2, $worker_test_count
238
+ assert_equal false, $worker_success
239
+ end # retry, bury
240
+
241
+ it "should support retrying jobs and succeeds" do
242
+ clear_jobs!('foo.bar')
243
+ Backburner.configure { |config| config.max_job_retries = 2; config.retry_delay = 0 }
244
+ Backburner::Worker.enqueue TestRetryJob, ["bam", "foo"], :queue => 'foo.bar'
245
+ out = []
246
+ 3.times do
247
+ out << silenced(2) do
248
+ worker = Backburner::Worker.new('foo.bar')
249
+ worker.prepare
250
+ worker.work_one_job
251
+ end
252
+ end
253
+ assert_match /attempt 1 of 3, retrying/, out.first
254
+ assert_match /attempt 2 of 3, retrying/, out[1]
255
+ assert_match /Finished TestRetryJob/m, out.last
256
+ refute_match(/failed/, out.last)
257
+ assert_equal 3, $worker_test_count
258
+ assert_equal true, $worker_success
259
+ end # retrying, succeeds
260
+
261
+ after do
262
+ Backburner.configure { |config| config.max_job_retries = 0; config.retry_delay = 5 }
263
+ end
181
264
  end # work_one_job
182
265
  end # Backburner::Worker
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backburner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-06 00:00:00.000000000 Z
12
+ date: 2012-11-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: beaneater
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 0.1.1
21
+ version: 0.1.2
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: 0.1.1
29
+ version: 0.1.2
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: dante
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -112,6 +112,7 @@ files:
112
112
  - examples/custom.rb
113
113
  - examples/demo.rb
114
114
  - examples/god.rb
115
+ - examples/retried.rb
115
116
  - examples/simple.rb
116
117
  - lib/backburner.rb
117
118
  - lib/backburner/async_proxy.rb