backburner 0.2.0 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,21 +1,25 @@
1
1
  # CHANGELOG
2
2
 
3
- ## Version 0.2.1 (Unreleased)
3
+ ## Version 0.2.6 (Unreleased)
4
4
 
5
- ## Version 0.2.0 (Nov 7th 2012)
5
+ ## Version 0.2.5 (Nov 9 2012)
6
+
7
+ * Add support for multiple worker processing strategies through subclassing.
8
+
9
+ ## Version 0.2.0 (Nov 7 2012)
6
10
 
7
11
  * Add new plugin hooks feature (see HOOKS.md)
8
12
 
9
- ## Version 0.1.2 (Nov 7th 2012)
13
+ ## Version 0.1.2 (Nov 7 2012)
10
14
 
11
15
  * Adds ability to specify a custom logger.
12
16
  * Adds job retry configuration and worker support.
13
17
 
14
- ## Version 0.1.1 (Nov 6th 2012)
18
+ ## Version 0.1.1 (Nov 6 2012)
15
19
 
16
20
  * Fix issue with timed out reserves
17
21
 
18
- ## Version 0.1.0 (Nov 4th 2012)
22
+ ## Version 0.1.0 (Nov 4 2012)
19
23
 
20
24
  * Switch to beaneater as new ruby beanstalkd client
21
25
  * Add support for array of connections in `beanstalk_url`
@@ -0,0 +1,37 @@
1
+ We love pull requests. Here's a quick guide:
2
+
3
+ 1. Fork the repo.
4
+
5
+ 2. Run the tests. We only take pull requests with passing tests, and it's great
6
+ to know that you have a clean slate: `bundle && rake test`
7
+
8
+ 3. Add a test for your change. Only refactoring and documentation changes
9
+ require no new tests. If you are adding functionality or fixing a bug, we need
10
+ a test!
11
+
12
+ 4. Make the test pass.
13
+
14
+ 5. Push to your fork and submit a pull request.
15
+
16
+ At this point you're waiting on us. We like to at least comment on, if not
17
+ accept, pull requests within three business days (and, typically, one business
18
+ day). We may suggest some changes or improvements or alternatives.
19
+
20
+ Some things that will increase the chance that your pull request is accepted:
21
+
22
+ * Use Rails idioms and helpers
23
+ * Include tests that fail without your code, and pass with it
24
+ * Update the documentation and README for anything affected by your contribution
25
+
26
+ Syntax:
27
+
28
+ * Two spaces, no tabs.
29
+ * No trailing whitespace. Blank lines should not have any space.
30
+ * Prefer &&/|| over and/or.
31
+ * MyClass.my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
32
+ * a = b and not a=b.
33
+ * Follow the conventions you see used in the source already.
34
+
35
+ And in case we didn't emphasize it enough: we love tests!
36
+
37
+ NOTE: Adapted from https://raw.github.com/thoughtbot/factory_girl_rails/master/CONTRIBUTING.md
data/README.md CHANGED
@@ -8,8 +8,11 @@ web framework, but is especially suited for use with [Sinatra](http://sinatrarb.
8
8
 
9
9
  If you want to use beanstalk for your job processing, consider using Backburner.
10
10
  Backburner is heavily inspired by Resque and DelayedJob. Backburner stores all jobs as simple JSON message payloads.
11
- Backburner can be a persistent queue when the beanstalk persistence mode is enabled.
12
- It supports multiple queues, priorities, delays, and timeouts.
11
+ Persistent queues are supported when beanstalkd persistence mode is enabled.
12
+
13
+ Backburner supports multiple queues, job priorities, delays, and timeouts. In addition,
14
+ Backburner has robust support for retrying failed jobs, handling error cases,
15
+ custom logging, and extensible plugin hooks.
13
16
 
14
17
  ## Why Backburner?
15
18
 
@@ -83,20 +86,24 @@ Backburner is extremely simple to setup. Just configure basic settings for backb
83
86
 
84
87
  ```ruby
85
88
  Backburner.configure do |config|
86
- config.beanstalk_url = ["beanstalk://127.0.0.1", "beanstalk://127.0.0.1:11301"]
89
+ config.beanstalk_url = ["beanstalk://127.0.0.1", "..."]
87
90
  config.tube_namespace = "some.app.production"
88
91
  config.on_error = lambda { |e| puts e }
89
92
  config.max_job_retries = 3 # default 0 retries
90
93
  config.retry_delay = 2 # default 5 seconds
91
94
  config.default_priority = 65536
92
95
  config.respond_timeout = 120
96
+ config.default_worker = Backburner::Workers::Simple
93
97
  config.logger = Logger.new(STDOUT)
94
98
  end
95
99
  ```
96
100
 
101
+ The key options available are:
102
+
97
103
  * The `beanstalk_url` supports a string such as 'beanstalk://127.0.0.1' or an array of addresses.
98
104
  * The `tube_namespace` is the prefix used for all tubes related to this backburner queue.
99
105
  * The `on_error` is a callback that gets invoked with the error whenever any job in the system fails.
106
+ * The `default_worker` is the processing worker that will be used if no other worker is specified.
100
107
  * The `max_job_retries` determines how many times to retry a job before burying
101
108
  * The `retry_delay` determines the base time to wait (in secs) between retries
102
109
  * The `logger` is the logger object written to when backburner wants to report info or errors.
@@ -203,6 +210,35 @@ bundle exec backburner newsletter-sender,push-message -d -P /var/run/backburner.
203
210
 
204
211
  This will daemonize the worker and store the pid and logs automatically.
205
212
 
213
+ ### Processing Strategies
214
+
215
+ In Backburner, there are actually multiple different strategies for processing jobs
216
+ which are reflected by multiple workers.
217
+ Custom workers can be [defined fairly easily](https://github.com/nesquena/backburner/wiki/Defining-Workers).
218
+ By default, Backburner comes with the following workers built-in:
219
+
220
+ | Worker | Description |
221
+ | ------- | ------------------------------- |
222
+ | `Backburner::Workers::Simple` | Single threaded, no forking worker. Simplest option. |
223
+
224
+ You can select the default worker for processing with:
225
+
226
+ ```ruby
227
+ Backburner.configure do |config|
228
+ config.default_worker = Backburner::Workers::Simple
229
+ end
230
+ ```
231
+
232
+ or determine the worker on the fly when invoking `work`:
233
+
234
+ ```ruby
235
+ Backburner.work('newsletter_sender', :worker => Backburner::Workers::Threaded)
236
+ ```
237
+
238
+ or when more official workers are supported, through alternate rake tasks.
239
+ Additional workers such as `threaded`, `forking` and `threads_on_fork` will hopefully be
240
+ developed in the future. If you are interested in helping, please let us know.
241
+
206
242
  ### Default Queues
207
243
 
208
244
  Workers can be easily restricted to processing only a specific set of queues as shown above. However, if you want a worker to
@@ -237,7 +273,7 @@ Backburner is highly extensible and can be tailored to your needs by using vario
237
273
  can be triggered across the job processing lifecycle.
238
274
  Often using hooks is much easier then trying to monkey patch the externals.
239
275
 
240
- Check out [HOOKS.md](HOOKS.md) for a detailed overview on using hooks.
276
+ Check out [HOOKS.md](https://github.com/nesquena/backburner/blob/master/HOOKS.md) for a detailed overview on using hooks.
241
277
 
242
278
  ### Failures
243
279
 
@@ -10,6 +10,7 @@ require 'backburner/connection'
10
10
  require 'backburner/hooks'
11
11
  require 'backburner/performable'
12
12
  require 'backburner/worker'
13
+ require 'backburner/workers/simple'
13
14
  require 'backburner/queue'
14
15
 
15
16
  module Backburner
@@ -28,9 +29,12 @@ module Backburner
28
29
  #
29
30
  # @example
30
31
  # Backburner.work('newsletter_sender', 'test_job')
32
+ # Backburner.work('newsletter_sender', 'test_job', :worker => NotSimpleWorker)
31
33
  #
32
34
  def work(*tubes)
33
- Backburner::Worker.start(tubes)
35
+ options = tubes.last.is_a?(Hash) ? tubes.pop : {}
36
+ worker_class = options[:worker] || configuration.default_worker
37
+ worker_class.start(tubes)
34
38
  end
35
39
 
36
40
  # Yields a configuration block
@@ -9,6 +9,7 @@ module Backburner
9
9
  attr_accessor :retry_delay # retry delay in seconds
10
10
  attr_accessor :default_queues # default queues
11
11
  attr_accessor :logger # logger
12
+ attr_accessor :default_worker # default worker class
12
13
 
13
14
  def initialize
14
15
  @beanstalk_url = "beanstalk://localhost"
@@ -20,6 +21,7 @@ module Backburner
20
21
  @retry_delay = 5
21
22
  @default_queues = []
22
23
  @logger = nil
24
+ @default_worker = Backburner::Workers::Simple
23
25
  end
24
26
  end # Configuration
25
27
  end # Backburner
@@ -1,3 +1,3 @@
1
1
  module Backburner
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.5"
3
3
  end
@@ -1,6 +1,10 @@
1
1
  require 'backburner/job'
2
2
 
3
3
  module Backburner
4
+ #
5
+ # @abstract Subclass and override {#process_tube_names}, {#prepare} and {#start} to implement
6
+ # a custom Worker class.
7
+ #
4
8
  class Worker
5
9
  include Backburner::Helpers
6
10
  include Backburner::Logger
@@ -12,7 +16,7 @@ module Backburner
12
16
  def known_queue_classes; @known_queue_classes ||= []; end
13
17
  end
14
18
 
15
- # Enqueues a job to be processed later by a worker
19
+ # Enqueues a job to be processed later by a worker.
16
20
  # Options: `pri` (priority), `delay` (delay in secs), `ttr` (time to respond), `queue` (queue name)
17
21
  #
18
22
  # @raise [Beaneater::NotConnected] If beanstalk fails to connect.
@@ -32,7 +36,7 @@ module Backburner
32
36
  return true
33
37
  end
34
38
 
35
- # Starts processing jobs in the specified tube_names
39
+ # Starts processing jobs with the specified tube_names.
36
40
  #
37
41
  # @example
38
42
  # Backburner::Worker.start(["foo.tube.name"])
@@ -41,7 +45,7 @@ module Backburner
41
45
  self.new(tube_names).start
42
46
  end
43
47
 
44
- # Returns the worker connection
48
+ # Returns the worker connection.
45
49
  # @example
46
50
  # Backburner::Worker.connection # => <Beaneater::Pool>
47
51
  def self.connection
@@ -51,45 +55,53 @@ module Backburner
51
55
  # List of tube names to be watched and processed
52
56
  attr_accessor :tube_names
53
57
 
58
+ # Constructs a new worker for processing jobs within specified tubes.
59
+ #
54
60
  # @example
55
61
  # Worker.new(['test.job'])
56
62
  def initialize(tube_names=nil)
57
- @tube_names = begin
58
- tube_names = tube_names.first if tube_names && tube_names.size == 1 && tube_names.first.is_a?(Array)
59
- tube_names = Array(tube_names).compact if tube_names && Array(tube_names).compact.size > 0
60
- tube_names = nil if tube_names && tube_names.compact.empty?
61
- tube_names
62
- end
63
+ @tube_names = self.process_tube_names(tube_names)
63
64
  end
64
65
 
65
- # Starts processing new jobs indefinitely
66
- # Primary way to consume and process jobs in specified tubes
66
+ # Starts processing ready jobs indefinitely.
67
+ # Primary way to consume and process jobs in specified tubes.
67
68
  #
68
69
  # @example
69
70
  # @worker.start
70
71
  #
71
72
  def start
72
- prepare
73
- loop { work_one_job }
73
+ raise NotImplementedError
74
74
  end
75
75
 
76
- # Setup beanstalk tube_names and watch all specified tubes for jobs.
77
- # Used to prepare job queues before processing jobs.
76
+ # Used to prepare the job queues before job processing is initiated.
78
77
  #
79
78
  # @raise [Beaneater::NotConnected] If beanstalk fails to connect.
80
79
  # @example
81
80
  # @worker.prepare
82
81
  #
82
+ # @abstract Define this in your worker subclass
83
+ # to be run once before processing. Recommended to watch tubes
84
+ # or print a message to the logs with 'log_info'
85
+ #
83
86
  def prepare
84
- self.tube_names ||= Backburner.default_queues.any? ? Backburner.default_queues : all_existing_queues
85
- self.tube_names = Array(self.tube_names)
86
- self.tube_names.map! { |name| expand_tube_name(name) }
87
- log_info "Working #{tube_names.size} queues: [ #{tube_names.join(', ')} ]"
88
- self.connection.tubes.watch!(*self.tube_names)
87
+ raise NotImplementedError
88
+ end
89
+
90
+ # Processes tube_names given tube_names array.
91
+ # Should return normalized tube_names as an array of strings.
92
+ #
93
+ # @example
94
+ # process_tube_names([['foo'], ['bar']])
95
+ # => ['foo', 'bar', 'baz']
96
+ #
97
+ # @note This method can be overridden in inherited workers
98
+ # to add more complex tube name processing.
99
+ def process_tube_names(tube_names)
100
+ compact_tube_names(tube_names)
89
101
  end
90
102
 
91
- # Reserves one job within the specified queues
92
- # Pops the job off and serializes the job to JSON
103
+ # Reserves one job within the specified queues.
104
+ # Pops the job off and serializes the job to JSON.
93
105
  # Each job is performed by invoking `perform` on the job class.
94
106
  #
95
107
  # @example
@@ -143,5 +155,16 @@ module Backburner
143
155
  end
144
156
  end
145
157
  end
158
+
159
+ # Normalizes tube names given array of tube_names
160
+ # Compacts nil items, flattens arrays, sets tubes to nil if no valid names
161
+ # Loads default tubes when no tubes given.
162
+ def compact_tube_names(tube_names)
163
+ tube_names = tube_names.first if tube_names && tube_names.size == 1 && tube_names.first.is_a?(Array)
164
+ tube_names = Array(tube_names).compact if tube_names && Array(tube_names).compact.size > 0
165
+ tube_names = nil if tube_names && tube_names.compact.empty?
166
+ tube_names ||= Backburner.default_queues.any? ? Backburner.default_queues : all_existing_queues
167
+ Array(tube_names)
168
+ end
146
169
  end # Worker
147
170
  end # Backburner
@@ -0,0 +1,29 @@
1
+ module Backburner
2
+ module Workers
3
+ class Simple < Worker
4
+ # Used to prepare job queues before processing jobs.
5
+ # Setup beanstalk tube_names and watch all specified tubes for jobs.
6
+ #
7
+ # @raise [Beaneater::NotConnected] If beanstalk fails to connect.
8
+ # @example
9
+ # @worker.prepare
10
+ #
11
+ def prepare
12
+ self.tube_names.map! { |name| expand_tube_name(name) }
13
+ log_info "Working #{tube_names.size} queues: [ #{tube_names.join(', ')} ]"
14
+ self.connection.tubes.watch!(*self.tube_names)
15
+ end
16
+
17
+ # Starts processing new jobs indefinitely.
18
+ # Primary way to consume and process jobs in specified tubes.
19
+ #
20
+ # @example
21
+ # @worker.start
22
+ #
23
+ def start
24
+ prepare
25
+ loop { work_one_job }
26
+ end
27
+ end # Basic
28
+ end # Workers
29
+ end # Backburner
@@ -13,6 +13,8 @@ class TestBackburnerJob
13
13
  end
14
14
  end
15
15
 
16
+ class TestWorker < Backburner::Worker; end
17
+
16
18
  describe "Backburner module" do
17
19
  before { Backburner.default_queues.clear }
18
20
 
@@ -21,7 +23,7 @@ describe "Backburner module" do
21
23
  Backburner.enqueue TestBackburnerJob, 5, 6
22
24
  Backburner.enqueue TestBackburnerJob, 15, 10
23
25
  silenced(2) do
24
- worker = Backburner::Worker.new('test.jobber')
26
+ worker = Backburner::Workers::Simple.new('test.jobber')
25
27
  worker.prepare
26
28
  2.times { worker.work_one_job }
27
29
  end
@@ -34,13 +36,24 @@ describe "Backburner module" do
34
36
  end # enqueue
35
37
 
36
38
  describe "for work method" do
37
- it "invokes worker start" do
38
- Backburner::Worker.expects(:start).with(["foo", "bar"])
39
+ it "invokes worker simple start" do
40
+ Backburner::Workers::Simple.expects(:start).with(["foo", "bar"])
41
+ Backburner.work("foo", "bar")
42
+ end
43
+
44
+ it "invokes other worker if specified in configuration" do
45
+ Backburner.configure { |config| config.default_worker = TestWorker }
46
+ TestWorker.expects(:start).with(["foo", "bar"])
39
47
  Backburner.work("foo", "bar")
40
48
  end
41
49
 
50
+ it "invokes other worker if specified in work method as options" do
51
+ TestWorker.expects(:start).with(["foo", "bar"])
52
+ Backburner.work("foo", "bar", :worker => TestWorker)
53
+ end
54
+
42
55
  it "invokes worker start with no args" do
43
- Backburner::Worker.expects(:start).with([])
56
+ Backburner::Workers::Simple.expects(:start).with([])
44
57
  Backburner.work
45
58
  end
46
59
  end # work!
@@ -58,4 +71,8 @@ describe "Backburner module" do
58
71
  assert_same_elements ["foo", "bar"], Backburner.default_queues
59
72
  end
60
73
  end
74
+
75
+ after do
76
+ Backburner.configure { |config| config.default_worker = Backburner::Workers::Simple }
77
+ end
61
78
  end # Backburner
@@ -0,0 +1,235 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+ require File.expand_path('../fixtures/test_jobs', __FILE__)
3
+ require File.expand_path('../fixtures/hooked', __FILE__)
4
+
5
+ describe "Backburner::Workers::Basic module" do
6
+ before do
7
+ Backburner.default_queues.clear
8
+ @worker_class = Backburner::Workers::Simple
9
+ end
10
+
11
+ describe "for prepare method" do
12
+ it "should watch specified tubes" do
13
+ worker = @worker_class.new(["foo", "bar"])
14
+ out = capture_stdout { worker.prepare }
15
+ assert_equal ["demo.test.foo", "demo.test.bar"], worker.tube_names
16
+ assert_same_elements ["demo.test.foo", "demo.test.bar"], @worker_class.connection.tubes.watched.map(&:name)
17
+ assert_match /demo\.test\.foo/, out
18
+ end # multiple
19
+
20
+ it "should watch single tube" do
21
+ worker = @worker_class.new("foo")
22
+ out = capture_stdout { worker.prepare }
23
+ assert_equal ["demo.test.foo"], worker.tube_names
24
+ assert_same_elements ["demo.test.foo"], @worker_class.connection.tubes.watched.map(&:name)
25
+ assert_match /demo\.test\.foo/, out
26
+ end # single
27
+
28
+ it "should respect default_queues settings" do
29
+ Backburner.default_queues.concat(["foo", "bar"])
30
+ worker = @worker_class.new
31
+ out = capture_stdout { worker.prepare }
32
+ assert_equal ["demo.test.foo", "demo.test.bar"], worker.tube_names
33
+ assert_same_elements ["demo.test.foo", "demo.test.bar"], @worker_class.connection.tubes.watched.map(&:name)
34
+ assert_match /demo\.test\.foo/, out
35
+ end
36
+
37
+ it "should assign based on all tubes" do
38
+ @worker_class.any_instance.expects(:all_existing_queues).once.returns("bar")
39
+ worker = @worker_class.new
40
+ out = capture_stdout { worker.prepare }
41
+ assert_equal ["demo.test.bar"], worker.tube_names
42
+ assert_same_elements ["demo.test.bar"], @worker_class.connection.tubes.watched.map(&:name)
43
+ assert_match /demo\.test\.bar/, out
44
+ end # all assign
45
+
46
+ it "should properly retrieve all tubes" do
47
+ worker = @worker_class.new
48
+ out = capture_stdout { worker.prepare }
49
+ assert_contains worker.tube_names, "demo.test.test-job"
50
+ assert_contains @worker_class.connection.tubes.watched.map(&:name), "demo.test.test-job"
51
+ assert_match /demo\.test\.test-job/, out
52
+ end # all read
53
+ end # prepare
54
+
55
+ describe "for work_one_job method" do
56
+ before do
57
+ $worker_test_count = 0
58
+ $worker_success = false
59
+ end
60
+
61
+ it "should work an enqueued job" do
62
+ clear_jobs!("foo.bar")
63
+ @worker_class.enqueue TestJob, [1, 2], :queue => "foo.bar"
64
+ silenced(2) do
65
+ worker = @worker_class.new('foo.bar')
66
+ worker.prepare
67
+ worker.work_one_job
68
+ end
69
+ assert_equal 3, $worker_test_count
70
+ end # enqueue
71
+
72
+ it "should fail quietly if there's an argument error" do
73
+ clear_jobs!("foo.bar")
74
+ @worker_class.enqueue TestJob, ["bam", "foo", "bar"], :queue => "foo.bar"
75
+ out = silenced(2) do
76
+ worker = @worker_class.new('foo.bar')
77
+ worker.prepare
78
+ worker.work_one_job
79
+ end
80
+ assert_match(/Exception ArgumentError/, out)
81
+ assert_equal 0, $worker_test_count
82
+ end # fail, argument
83
+
84
+ it "should work an enqueued failing job" do
85
+ clear_jobs!("foo.bar")
86
+ @worker_class.enqueue TestFailJob, [1, 2], :queue => "foo.bar"
87
+ Backburner::Job.any_instance.expects(:bury).once
88
+ out = silenced(2) do
89
+ worker = @worker_class.new('foo.bar')
90
+ worker.prepare
91
+ worker.work_one_job
92
+ end
93
+ assert_match(/Exception RuntimeError/, out)
94
+ assert_equal 0, $worker_test_count
95
+ end # fail, runtime error
96
+
97
+ it "should work an invalid job parsed" do
98
+ Beaneater::Tubes.any_instance.expects(:reserve).returns(stub(:body => "{%$^}", :bury => true))
99
+ out = silenced(2) do
100
+ worker = @worker_class.new('foo.bar')
101
+ worker.prepare
102
+ worker.work_one_job
103
+ end
104
+ assert_match(/Exception Backburner::Job::JobFormatInvalid/, out)
105
+ assert_equal 0, $worker_test_count
106
+ end # fail, runtime error
107
+
108
+ it "should work for an async job" do
109
+ clear_jobs!('foo.bar')
110
+ TestAsyncJob.async(:queue => 'foo.bar').foo(3, 5)
111
+ silenced(2) do
112
+ worker = @worker_class.new('foo.bar')
113
+ worker.prepare
114
+ worker.work_one_job
115
+ end
116
+ assert_equal 15, $worker_test_count
117
+ end # async
118
+
119
+ it "should support retrying jobs and burying" do
120
+ clear_jobs!('foo.bar')
121
+ Backburner.configure { |config| config.max_job_retries = 1; config.retry_delay = 0 }
122
+ @worker_class.enqueue TestRetryJob, ["bam", "foo"], :queue => 'foo.bar'
123
+ out = []
124
+ 2.times do
125
+ out << silenced(2) do
126
+ worker = @worker_class.new('foo.bar')
127
+ worker.prepare
128
+ worker.work_one_job
129
+ end
130
+ end
131
+ assert_match /attempt 1 of 2, retrying/, out.first
132
+ assert_match /Finished TestRetryJob/m, out.last
133
+ assert_match /attempt 2 of 2, burying/m, out.last
134
+ assert_equal 2, $worker_test_count
135
+ assert_equal false, $worker_success
136
+ end # retry, bury
137
+
138
+ it "should support retrying jobs and succeeds" do
139
+ clear_jobs!('foo.bar')
140
+ Backburner.configure { |config| config.max_job_retries = 2; config.retry_delay = 0 }
141
+ @worker_class.enqueue TestRetryJob, ["bam", "foo"], :queue => 'foo.bar'
142
+ out = []
143
+ 3.times do
144
+ out << silenced(2) do
145
+ worker = @worker_class.new('foo.bar')
146
+ worker.prepare
147
+ worker.work_one_job
148
+ end
149
+ end
150
+ assert_match /attempt 1 of 3, retrying/, out.first
151
+ assert_match /attempt 2 of 3, retrying/, out[1]
152
+ assert_match /Finished TestRetryJob/m, out.last
153
+ refute_match(/failed/, out.last)
154
+ assert_equal 3, $worker_test_count
155
+ assert_equal true, $worker_success
156
+ end # retrying, succeeds
157
+
158
+ it "should support event hooks without retry" do
159
+ $hooked_fail_count = 0
160
+ clear_jobs!('foo.bar.events')
161
+ out = silenced(2) do
162
+ HookedObjectSuccess.async(:queue => 'foo.bar.events').foo(5)
163
+ worker = @worker_class.new('foo.bar.events')
164
+ worker.prepare
165
+ worker.work_one_job
166
+ end
167
+ assert_match /before_enqueue.*after_enqueue.*Working 1 queues/m, out
168
+ assert_match /!!before_enqueue_bar!! \[nil, :foo, 5\]/, out
169
+ assert_match /!!after_enqueue_bar!! \[nil, :foo, 5\]/, out
170
+ assert_match /!!before_perform_foo!! \[nil, "foo", 5\]/, out
171
+ assert_match /!!BEGIN around_perform_bar!! \[nil, "foo", 5\]/, out
172
+ assert_match /!!BEGIN around_perform_cat!! \[nil, "foo", 5\]/, out
173
+ assert_match /!!on_failure_foo!!.*HookFailError/, out
174
+ assert_match /attempt 1 of 1, burying/, out
175
+ end # event hooks, no retry
176
+
177
+ it "should support event hooks with retry" do
178
+ $hooked_fail_count = 0
179
+ clear_jobs!('foo.bar.events.retry')
180
+ Backburner.configure { |config| config.max_job_retries = 1; config.retry_delay = 0 }
181
+ out = silenced(2) do
182
+ HookedObjectSuccess.async(:queue => 'foo.bar.events.retry').foo(5)
183
+ worker = @worker_class.new('foo.bar.events.retry')
184
+ worker.prepare
185
+ 2.times do
186
+ worker.work_one_job
187
+ end
188
+ end
189
+ assert_match /before_enqueue.*after_enqueue.*Working 1 queues/m, out
190
+ assert_match /!!before_enqueue_bar!! \[nil, :foo, 5\]/, out
191
+ assert_match /!!after_enqueue_bar!! \[nil, :foo, 5\]/, out
192
+ assert_match /!!before_perform_foo!! \[nil, "foo", 5\]/, out
193
+ assert_match /!!BEGIN around_perform_bar!! \[nil, "foo", 5\]/, out
194
+ assert_match /!!BEGIN around_perform_cat!! \[nil, "foo", 5\]/, out
195
+ assert_match /!!on_failure_foo!!.*HookFailError/, out
196
+ assert_match /!!on_failure_foo!!.*retrying.*around_perform_bar.*around_perform_cat/m, out
197
+ assert_match /attempt 1 of 2, retrying/, out
198
+ assert_match /!!before_perform_foo!! \[nil, "foo", 5\]/, out
199
+ assert_match /!!END around_perform_bar!! \[nil, "foo", 5\]/, out
200
+ assert_match /!!END around_perform_cat!! \[nil, "foo", 5\]/, out
201
+ assert_match /!!after_perform_foo!! \[nil, "foo", 5\]/, out
202
+ assert_match /Finished HookedObjectSuccess/, out
203
+ end # event hooks, with retry
204
+
205
+ it "should support event hooks with stopping enqueue" do
206
+ $hooked_fail_count = 0
207
+ clear_jobs!('foo.bar.events.retry2')
208
+ out = silenced(2) do
209
+ HookedObjectBeforeEnqueueFail.async(:queue => 'foo.bar.events.retry2').foo(5)
210
+ end
211
+ expanded_tube = [Backburner.configuration.tube_namespace, 'foo.bar.events.retry2'].join(".")
212
+ assert_nil @worker_class.connection.tubes[expanded_tube].peek(:ready)
213
+ end # stopping enqueue
214
+
215
+ it "should support event hooks with stopping perform" do
216
+ $hooked_fail_count = 0
217
+ clear_jobs!('foo.bar.events.retry3')
218
+ expanded_tube = [Backburner.configuration.tube_namespace, 'foo.bar.events.retry3'].join(".")
219
+ out = silenced(2) do
220
+ HookedObjectBeforePerformFail.async(:queue => 'foo.bar.events.retry3').foo(10)
221
+ worker = @worker_class.new('foo.bar.events.retry3')
222
+ worker.prepare
223
+ worker.work_one_job
224
+ end
225
+ assert_match /!!before_perform_foo!! \[nil, "foo", 10\]/, out
226
+ assert_match /before_perform_foo.*Finished/m, out
227
+ refute_match(/Fail ran!!/, out)
228
+ refute_match(/HookFailError/, out)
229
+ end # stopping perform
230
+
231
+ after do
232
+ Backburner.configure { |config| config.max_job_retries = 0; config.retry_delay = 5 }
233
+ end
234
+ end # work_one_job
235
+ end # Worker
@@ -54,6 +54,11 @@ describe "Backburner::Worker module" do
54
54
  end # connection
55
55
 
56
56
  describe "for tube_names accessor" do
57
+ before do
58
+ Backburner.default_queues << "baz"
59
+ Backburner.default_queues << "bam"
60
+ end
61
+
57
62
  it "supports retrieving tubes" do
58
63
  worker = Backburner::Worker.new(["foo", "bar"])
59
64
  assert_equal ["foo", "bar"], worker.tube_names
@@ -64,9 +69,9 @@ describe "Backburner::Worker module" do
64
69
  assert_equal ["foo", "bar"], worker.tube_names
65
70
  end
66
71
 
67
- it "supports empty nil array arg" do
72
+ it "supports empty nil array arg with default values" do
68
73
  worker = Backburner::Worker.new([nil])
69
- assert_equal nil, worker.tube_names
74
+ assert_equal ['baz', 'bam'], worker.tube_names
70
75
  end
71
76
 
72
77
  it "supports single tube arg" do
@@ -74,239 +79,14 @@ describe "Backburner::Worker module" do
74
79
  assert_equal ["foo"], worker.tube_names
75
80
  end
76
81
 
77
- it "supports empty array arg" do
82
+ it "supports empty array arg with default values" do
78
83
  worker = Backburner::Worker.new([])
79
- assert_equal nil, worker.tube_names
84
+ assert_equal ['baz', 'bam'], worker.tube_names
80
85
  end
81
86
 
82
- it "supports nil arg" do
87
+ it "supports nil arg with default values" do
83
88
  worker = Backburner::Worker.new(nil)
84
- assert_equal nil, worker.tube_names
89
+ assert_equal ['baz', 'bam'], worker.tube_names
85
90
  end
86
91
  end # tube_names
87
-
88
- describe "for prepare method" do
89
- it "should watch specified tubes" do
90
- worker = Backburner::Worker.new(["foo", "bar"])
91
- out = capture_stdout { worker.prepare }
92
- assert_equal ["demo.test.foo", "demo.test.bar"], worker.tube_names
93
- assert_same_elements ["demo.test.foo", "demo.test.bar"], Backburner::Worker.connection.tubes.watched.map(&:name)
94
- assert_match /demo\.test\.foo/, out
95
- end # multiple
96
-
97
- it "should watch single tube" do
98
- worker = Backburner::Worker.new("foo")
99
- out = capture_stdout { worker.prepare }
100
- assert_equal ["demo.test.foo"], worker.tube_names
101
- assert_same_elements ["demo.test.foo"], Backburner::Worker.connection.tubes.watched.map(&:name)
102
- assert_match /demo\.test\.foo/, out
103
- end # single
104
-
105
- it "should respect default_queues settings" do
106
- Backburner.default_queues.concat(["foo", "bar"])
107
- worker = Backburner::Worker.new
108
- out = capture_stdout { worker.prepare }
109
- assert_equal ["demo.test.foo", "demo.test.bar"], worker.tube_names
110
- assert_same_elements ["demo.test.foo", "demo.test.bar"], Backburner::Worker.connection.tubes.watched.map(&:name)
111
- assert_match /demo\.test\.foo/, out
112
- end
113
-
114
- it "should assign based on all tubes" do
115
- Backburner::Worker.any_instance.expects(:all_existing_queues).once.returns("bar")
116
- worker = Backburner::Worker.new
117
- out = capture_stdout { worker.prepare }
118
- assert_equal ["demo.test.bar"], worker.tube_names
119
- assert_same_elements ["demo.test.bar"], Backburner::Worker.connection.tubes.watched.map(&:name)
120
- assert_match /demo\.test\.bar/, out
121
- end # all assign
122
-
123
- it "should properly retrieve all tubes" do
124
- worker = Backburner::Worker.new
125
- out = capture_stdout { worker.prepare }
126
- assert_contains worker.tube_names, "demo.test.test-job"
127
- assert_contains Backburner::Worker.connection.tubes.watched.map(&:name), "demo.test.test-job"
128
- assert_match /demo\.test\.test-job/, out
129
- end # all read
130
- end # prepare
131
-
132
- describe "for work_one_job method" do
133
- before do
134
- $worker_test_count = 0
135
- $worker_success = false
136
- end
137
-
138
- it "should work an enqueued job" do
139
- clear_jobs!("foo.bar")
140
- Backburner::Worker.enqueue TestJob, [1, 2], :queue => "foo.bar"
141
- silenced(2) do
142
- worker = Backburner::Worker.new('foo.bar')
143
- worker.prepare
144
- worker.work_one_job
145
- end
146
- assert_equal 3, $worker_test_count
147
- end # enqueue
148
-
149
- it "should fail quietly if there's an argument error" do
150
- clear_jobs!("foo.bar")
151
- Backburner::Worker.enqueue TestJob, ["bam", "foo", "bar"], :queue => "foo.bar"
152
- out = silenced(2) do
153
- worker = Backburner::Worker.new('foo.bar')
154
- worker.prepare
155
- worker.work_one_job
156
- end
157
- assert_match(/Exception ArgumentError/, out)
158
- assert_equal 0, $worker_test_count
159
- end # fail, argument
160
-
161
- it "should work an enqueued failing job" do
162
- clear_jobs!("foo.bar")
163
- Backburner::Worker.enqueue TestFailJob, [1, 2], :queue => "foo.bar"
164
- Backburner::Job.any_instance.expects(:bury).once
165
- out = silenced(2) do
166
- worker = Backburner::Worker.new('foo.bar')
167
- worker.prepare
168
- worker.work_one_job
169
- end
170
- assert_match(/Exception RuntimeError/, out)
171
- assert_equal 0, $worker_test_count
172
- end # fail, runtime error
173
-
174
- it "should work an invalid job parsed" do
175
- Beaneater::Tubes.any_instance.expects(:reserve).returns(stub(:body => "{%$^}", :bury => true))
176
- out = silenced(2) do
177
- worker = Backburner::Worker.new('foo.bar')
178
- worker.prepare
179
- worker.work_one_job
180
- end
181
- assert_match(/Exception Backburner::Job::JobFormatInvalid/, out)
182
- assert_equal 0, $worker_test_count
183
- end # fail, runtime error
184
-
185
- it "should work for an async job" do
186
- clear_jobs!('foo.bar')
187
- TestAsyncJob.async(:queue => 'foo.bar').foo(3, 5)
188
- silenced(2) do
189
- worker = Backburner::Worker.new('foo.bar')
190
- worker.prepare
191
- worker.work_one_job
192
- end
193
- assert_equal 15, $worker_test_count
194
- end # async
195
-
196
- it "should support retrying jobs and burying" do
197
- clear_jobs!('foo.bar')
198
- Backburner.configure { |config| config.max_job_retries = 1; config.retry_delay = 0 }
199
- Backburner::Worker.enqueue TestRetryJob, ["bam", "foo"], :queue => 'foo.bar'
200
- out = []
201
- 2.times do
202
- out << silenced(2) do
203
- worker = Backburner::Worker.new('foo.bar')
204
- worker.prepare
205
- worker.work_one_job
206
- end
207
- end
208
- assert_match /attempt 1 of 2, retrying/, out.first
209
- assert_match /Finished TestRetryJob/m, out.last
210
- assert_match /attempt 2 of 2, burying/m, out.last
211
- assert_equal 2, $worker_test_count
212
- assert_equal false, $worker_success
213
- end # retry, bury
214
-
215
- it "should support retrying jobs and succeeds" do
216
- clear_jobs!('foo.bar')
217
- Backburner.configure { |config| config.max_job_retries = 2; config.retry_delay = 0 }
218
- Backburner::Worker.enqueue TestRetryJob, ["bam", "foo"], :queue => 'foo.bar'
219
- out = []
220
- 3.times do
221
- out << silenced(2) do
222
- worker = Backburner::Worker.new('foo.bar')
223
- worker.prepare
224
- worker.work_one_job
225
- end
226
- end
227
- assert_match /attempt 1 of 3, retrying/, out.first
228
- assert_match /attempt 2 of 3, retrying/, out[1]
229
- assert_match /Finished TestRetryJob/m, out.last
230
- refute_match(/failed/, out.last)
231
- assert_equal 3, $worker_test_count
232
- assert_equal true, $worker_success
233
- end # retrying, succeeds
234
-
235
- it "should support event hooks without retry" do
236
- $hooked_fail_count = 0
237
- clear_jobs!('foo.bar.events')
238
- out = silenced(2) do
239
- HookedObjectSuccess.async(:queue => 'foo.bar.events').foo(5)
240
- worker = Backburner::Worker.new('foo.bar.events')
241
- worker.prepare
242
- worker.work_one_job
243
- end
244
- assert_match /before_enqueue.*after_enqueue.*Working 1 queues/m, out
245
- assert_match /!!before_enqueue_bar!! \[nil, :foo, 5\]/, out
246
- assert_match /!!after_enqueue_bar!! \[nil, :foo, 5\]/, out
247
- assert_match /!!before_perform_foo!! \[nil, "foo", 5\]/, out
248
- assert_match /!!BEGIN around_perform_bar!! \[nil, "foo", 5\]/, out
249
- assert_match /!!BEGIN around_perform_cat!! \[nil, "foo", 5\]/, out
250
- assert_match /!!on_failure_foo!!.*HookFailError/, out
251
- assert_match /attempt 1 of 1, burying/, out
252
- end # event hooks, no retry
253
-
254
- it "should support event hooks with retry" do
255
- $hooked_fail_count = 0
256
- clear_jobs!('foo.bar.events.retry')
257
- Backburner.configure { |config| config.max_job_retries = 1; config.retry_delay = 0 }
258
- out = silenced(2) do
259
- HookedObjectSuccess.async(:queue => 'foo.bar.events.retry').foo(5)
260
- worker = Backburner::Worker.new('foo.bar.events.retry')
261
- worker.prepare
262
- 2.times do
263
- worker.work_one_job
264
- end
265
- end
266
- assert_match /before_enqueue.*after_enqueue.*Working 1 queues/m, out
267
- assert_match /!!before_enqueue_bar!! \[nil, :foo, 5\]/, out
268
- assert_match /!!after_enqueue_bar!! \[nil, :foo, 5\]/, out
269
- assert_match /!!before_perform_foo!! \[nil, "foo", 5\]/, out
270
- assert_match /!!BEGIN around_perform_bar!! \[nil, "foo", 5\]/, out
271
- assert_match /!!BEGIN around_perform_cat!! \[nil, "foo", 5\]/, out
272
- assert_match /!!on_failure_foo!!.*HookFailError/, out
273
- assert_match /!!on_failure_foo!!.*retrying.*around_perform_bar.*around_perform_cat/m, out
274
- assert_match /attempt 1 of 2, retrying/, out
275
- assert_match /!!before_perform_foo!! \[nil, "foo", 5\]/, out
276
- assert_match /!!END around_perform_bar!! \[nil, "foo", 5\]/, out
277
- assert_match /!!END around_perform_cat!! \[nil, "foo", 5\]/, out
278
- assert_match /!!after_perform_foo!! \[nil, "foo", 5\]/, out
279
- assert_match /Finished HookedObjectSuccess/, out
280
- end # event hooks, with retry
281
-
282
- it "should support event hooks with stopping enqueue" do
283
- $hooked_fail_count = 0
284
- clear_jobs!('foo.bar.events.retry2')
285
- out = silenced(2) do
286
- HookedObjectBeforeEnqueueFail.async(:queue => 'foo.bar.events.retry2').foo(5)
287
- end
288
- expanded_tube = [Backburner.configuration.tube_namespace, 'foo.bar.events.retry2'].join(".")
289
- assert_nil Backburner::Worker.connection.tubes[expanded_tube].peek(:ready)
290
- end # stopping enqueue
291
-
292
- it "should support event hooks with stopping perform" do
293
- $hooked_fail_count = 0
294
- clear_jobs!('foo.bar.events.retry3')
295
- expanded_tube = [Backburner.configuration.tube_namespace, 'foo.bar.events.retry3'].join(".")
296
- out = silenced(2) do
297
- HookedObjectBeforePerformFail.async(:queue => 'foo.bar.events.retry3').foo(10)
298
- worker = Backburner::Worker.new('foo.bar.events.retry3')
299
- worker.prepare
300
- worker.work_one_job
301
- end
302
- assert_match /!!before_perform_foo!! \[nil, "foo", 10\]/, out
303
- assert_match /before_perform_foo.*Finished/m, out
304
- refute_match(/Fail ran!!/, out)
305
- refute_match(/HookFailError/, out)
306
- end # stopping perform
307
-
308
- after do
309
- Backburner.configure { |config| config.max_job_retries = 0; config.retry_delay = 5 }
310
- end
311
- end # work_one_job
312
92
  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.2.0
4
+ version: 0.2.5
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-07 00:00:00.000000000 Z
12
+ date: 2012-11-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: beaneater
@@ -102,6 +102,7 @@ files:
102
102
  - .gitignore
103
103
  - .travis.yml
104
104
  - CHANGELOG.md
105
+ - CONTRIBUTING.md
105
106
  - Gemfile
106
107
  - HOOKS.md
107
108
  - LICENSE
@@ -129,6 +130,7 @@ files:
129
130
  - lib/backburner/tasks.rb
130
131
  - lib/backburner/version.rb
131
132
  - lib/backburner/worker.rb
133
+ - lib/backburner/workers/simple.rb
132
134
  - test/back_burner_test.rb
133
135
  - test/connection_test.rb
134
136
  - test/fixtures/hooked.rb
@@ -139,6 +141,7 @@ files:
139
141
  - test/logger_test.rb
140
142
  - test/performable_test.rb
141
143
  - test/queue_test.rb
144
+ - test/simple_worker_test.rb
142
145
  - test/test_helper.rb
143
146
  - test/worker_test.rb
144
147
  homepage: http://github.com/nesquena/backburner
@@ -176,5 +179,6 @@ test_files:
176
179
  - test/logger_test.rb
177
180
  - test/performable_test.rb
178
181
  - test/queue_test.rb
182
+ - test/simple_worker_test.rb
179
183
  - test/test_helper.rb
180
184
  - test/worker_test.rb