backburner 0.3.0 → 0.3.1
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.
- data/CHANGELOG.md +5 -1
- data/README.md +5 -3
- data/examples/stress.rb +3 -2
- data/lib/backburner.rb +1 -0
- data/lib/backburner/tasks.rb +9 -0
- data/lib/backburner/version.rb +1 -1
- data/lib/backburner/workers/forking.rb +48 -0
- data/test/fixtures/test_forking_jobs.rb +56 -0
- data/test/workers/forking_worker_test.rb +179 -0
- data/test/workers/{threads_on_fork_test.rb → threads_on_fork_worker_test.rb} +4 -0
- metadata +9 -5
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -254,21 +254,22 @@ This is why our examples use object IDs instead of passing around objects.
|
|
254
254
|
|
255
255
|
### Processing Strategies
|
256
256
|
|
257
|
-
In Backburner, there are
|
258
|
-
which are reflected by multiple
|
257
|
+
In Backburner, there are several different strategies for processing jobs
|
258
|
+
which are reflected by multiple worker subclasses.
|
259
259
|
Custom workers can be [defined fairly easily](https://github.com/nesquena/backburner/wiki/Defining-Workers).
|
260
260
|
By default, Backburner comes with the following workers built-in:
|
261
261
|
|
262
262
|
| Worker | Description |
|
263
263
|
| ------- | ------------------------------- |
|
264
264
|
| `Backburner::Workers::Simple` | Single threaded, no forking worker. Simplest option. |
|
265
|
+
| `Backburner::Workers::Forking` | Basic forking worker that manages crashes and memory bloat. |
|
265
266
|
| `Backburner::Workers::ThreadsOnFork` | Forking worker that utilizes threads for concurrent processing. |
|
266
267
|
|
267
268
|
You can select the default worker for processing with:
|
268
269
|
|
269
270
|
```ruby
|
270
271
|
Backburner.configure do |config|
|
271
|
-
config.default_worker = Backburner::Workers::
|
272
|
+
config.default_worker = Backburner::Workers::Forking
|
272
273
|
end
|
273
274
|
```
|
274
275
|
|
@@ -400,6 +401,7 @@ jobs processed by your beanstalk workers. An excellent addition to your Backburn
|
|
400
401
|
* [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
|
401
402
|
* [Miso](http://gomiso.com) - Open-source friendly place to work
|
402
403
|
* [Renan T. Fernandes](https://github.com/ShadowBelmolve) - Added threads_on_fork worker
|
404
|
+
* [Daniel Farrell](https://github.com/danielfarrell) - Added forking worker
|
403
405
|
|
404
406
|
## Contributing
|
405
407
|
|
data/examples/stress.rb
CHANGED
@@ -26,5 +26,6 @@ end
|
|
26
26
|
Backburner.enqueue TestJob, i
|
27
27
|
end
|
28
28
|
|
29
|
-
# Work tasks using
|
30
|
-
|
29
|
+
# Work tasks using threads_on_fork worker
|
30
|
+
# twitter tube will have 10 threads, garbage after 1000 executions and retry jobs 1 times.
|
31
|
+
Backburner.work("test-job:10:100:1", :worker => Backburner::Workers::ThreadsOnFork)
|
data/lib/backburner.rb
CHANGED
data/lib/backburner/tasks.rb
CHANGED
@@ -18,6 +18,15 @@ namespace :backburner do
|
|
18
18
|
end
|
19
19
|
end # simple
|
20
20
|
|
21
|
+
namespace :forking do
|
22
|
+
# QUEUE=foo,bar,baz rake backburner:forking:work
|
23
|
+
desc "Starts backburner worker using fork processing"
|
24
|
+
task :work => :environment do
|
25
|
+
queues = (ENV["QUEUE"] ? ENV["QUEUE"].split(',') : nil) rescue nil
|
26
|
+
Backburner.work queues, :worker => Backburner::Workers::Forking
|
27
|
+
end
|
28
|
+
end # forking
|
29
|
+
|
21
30
|
namespace :threads_on_fork do
|
22
31
|
# QUEUE=twitter:10:5000:5,parse_page,send_mail,verify_bithday THREADS=2 GARBAGE=1000 rake backburner:threads_on_fork:work
|
23
32
|
# twitter tube will have 10 threads, garbage after 5k executions and retry 5 times.
|
data/lib/backburner/version.rb
CHANGED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Backburner
|
2
|
+
module Workers
|
3
|
+
class Forking < 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 { fork_one_job }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Need to re-establish the connection to the server(s) after forking
|
29
|
+
# Waits for a job, works the job, and exits
|
30
|
+
def fork_one_job
|
31
|
+
pid = Process.fork do
|
32
|
+
@connection = Connection.new(Backburner.configuration.beanstalk_url)
|
33
|
+
work_one_job
|
34
|
+
coolest_exit
|
35
|
+
end
|
36
|
+
Process.wait(pid)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Exit with Kernel.exit! to avoid at_exit callbacks that should belongs to
|
40
|
+
# parent process
|
41
|
+
# We will use exitcode 99 that means the fork reached the garbage number
|
42
|
+
def coolest_exit
|
43
|
+
Kernel.exit! 99
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class ResponseForkingJob
|
2
|
+
include Backburner::Queue
|
3
|
+
queue_priority 1000
|
4
|
+
def self.perform(data)
|
5
|
+
$worker_test_count += data['worker_test_count'].to_i if data['worker_test_count']
|
6
|
+
$worker_success = data['worker_success'] if data['worker_success']
|
7
|
+
$worker_test_count = data['worker_test_count_set'].to_i if data['worker_test_count_set']
|
8
|
+
$worker_raise = data['worker_raise'] if data['worker_raise']
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class TestJobForking
|
13
|
+
include Backburner::Queue
|
14
|
+
queue_priority 1000
|
15
|
+
def self.perform(x, y)
|
16
|
+
Backburner::Workers::Forking.enqueue ResponseForkingJob, [{
|
17
|
+
:worker_test_count_set => x + y
|
18
|
+
}], :queue => 'response'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class TestFailJobForking
|
23
|
+
include Backburner::Queue
|
24
|
+
def self.perform(x, y)
|
25
|
+
Backburner::Workers::Forking.enqueue ResponseForkingJob, [{
|
26
|
+
:worker_raise => true
|
27
|
+
}], :queue => 'response'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class TestRetryJobForking
|
32
|
+
include Backburner::Queue
|
33
|
+
def self.perform(x, y)
|
34
|
+
if $worker_test_count <= 2
|
35
|
+
Backburner::Workers::Forking.enqueue ResponseForkingJob, [{
|
36
|
+
:worker_test_count => 1
|
37
|
+
}], :queue => 'response'
|
38
|
+
|
39
|
+
raise RuntimeError
|
40
|
+
else # succeeds
|
41
|
+
Backburner::Workers::Forking.enqueue ResponseForkingJob, [{
|
42
|
+
:worker_test_count => 1,
|
43
|
+
:worker_success => true
|
44
|
+
}], :queue => 'response'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class TestAsyncJobForking
|
50
|
+
include Backburner::Performable
|
51
|
+
def self.foo(x, y)
|
52
|
+
Backburner::Workers::Forking.enqueue ResponseForkingJob, [{
|
53
|
+
:worker_test_count_set => x * y
|
54
|
+
}], :queue => 'response'
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
require File.expand_path('../../fixtures/test_forking_jobs', __FILE__)
|
3
|
+
|
4
|
+
describe "Backburner::Workers::Forking module" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
Backburner.default_queues.clear
|
8
|
+
@worker_class = Backburner::Workers::Forking
|
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 fork_one_job method" do
|
56
|
+
|
57
|
+
it "should fork, reconnect, work job, and exit" do
|
58
|
+
clear_jobs!("bar.foo")
|
59
|
+
@worker_class.enqueue TestJobForking, [1, 2], :queue => "bar.foo"
|
60
|
+
|
61
|
+
fake_pid = 45
|
62
|
+
Process.expects(:fork).returns(fake_pid) do |&block|
|
63
|
+
Connection.expects(:new).with(Backburner.configuration.beanstalk_url)
|
64
|
+
@worker_class.any_instance.expects(:work_one_job)
|
65
|
+
@worker_class.any_instance.expects(:coolest_exit)
|
66
|
+
block.call
|
67
|
+
end
|
68
|
+
Process.expects(:wait).with(fake_pid)
|
69
|
+
|
70
|
+
silenced(2) do
|
71
|
+
worker = @worker_class.new('bar.foo')
|
72
|
+
worker.prepare
|
73
|
+
worker.fork_one_job
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end # fork_one_job
|
78
|
+
|
79
|
+
describe "practical tests" do
|
80
|
+
|
81
|
+
before do
|
82
|
+
@templogger = Templogger.new('/tmp')
|
83
|
+
Backburner.configure { |config| config.logger = @templogger.logger }
|
84
|
+
$worker_test_count = 0
|
85
|
+
$worker_success = false
|
86
|
+
$worker_raise = false
|
87
|
+
clear_jobs!('response')
|
88
|
+
clear_jobs!('bar.foo.1', 'bar.foo.2', 'bar.foo.3', 'bar.foo.4', 'bar.foo.5')
|
89
|
+
silenced do
|
90
|
+
@response_worker = @worker_class.new('response')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
after do
|
95
|
+
@templogger.close
|
96
|
+
clear_jobs!('response')
|
97
|
+
clear_jobs!('bar.foo.1', 'bar.foo.2', 'bar.foo.3', 'bar.foo.4', 'bar.foo.5')
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
it "should work an enqueued job" do
|
102
|
+
@worker = @worker_class.new('bar.foo.1')
|
103
|
+
@worker_class.enqueue TestJobForking, [1, 2], :queue => "bar.foo.1"
|
104
|
+
@worker.prepare
|
105
|
+
silenced(2) do
|
106
|
+
@worker.fork_one_job
|
107
|
+
@templogger.wait_for_match(/Completed TestJobFork/m)
|
108
|
+
@response_worker.prepare
|
109
|
+
@response_worker.work_one_job
|
110
|
+
end
|
111
|
+
assert_equal 3, $worker_test_count
|
112
|
+
end # enqueue
|
113
|
+
|
114
|
+
it "should work for an async job" do
|
115
|
+
@worker = @worker_class.new('bar.foo.2')
|
116
|
+
TestAsyncJobForking.async(:queue => 'bar.foo.2').foo(3, 5)
|
117
|
+
@worker.prepare
|
118
|
+
silenced(4) do
|
119
|
+
@worker.fork_one_job
|
120
|
+
@templogger.wait_for_match(/Completed TestAsyncJobFork/m)
|
121
|
+
@response_worker.prepare
|
122
|
+
@response_worker.work_one_job
|
123
|
+
end
|
124
|
+
assert_equal 15, $worker_test_count
|
125
|
+
end # async
|
126
|
+
|
127
|
+
it "should fail quietly if there's an argument error" do
|
128
|
+
Backburner.configure { |config| config.max_job_retries = 0 }
|
129
|
+
@worker = @worker_class.new('bar.foo.3')
|
130
|
+
@worker_class.enqueue TestJobForking, ["bam", "foo", "bar"], :queue => "bar.foo.3"
|
131
|
+
@worker.prepare
|
132
|
+
silenced(5) do
|
133
|
+
@worker.fork_one_job
|
134
|
+
@templogger.wait_for_match(/Finished TestJobFork.*attempt 1 of 1/m)
|
135
|
+
end
|
136
|
+
assert_match(/Exception ArgumentError/, @templogger.body)
|
137
|
+
assert_equal 0, $worker_test_count
|
138
|
+
end # fail, argument
|
139
|
+
|
140
|
+
it "should support retrying jobs and burying" do
|
141
|
+
Backburner.configure { |config| config.max_job_retries = 1; config.retry_delay = 0 }
|
142
|
+
@worker = @worker_class.new('bar.foo.4')
|
143
|
+
@worker_class.enqueue TestRetryJobForking, ["bam", "foo"], :queue => 'bar.foo.4'
|
144
|
+
@worker.prepare
|
145
|
+
silenced(4) do
|
146
|
+
2.times do
|
147
|
+
$worker_test_count += 1
|
148
|
+
@worker.fork_one_job
|
149
|
+
end
|
150
|
+
@templogger.wait_for_match(/Finished TestRetryJobFork.*attempt 2 of 2/m)
|
151
|
+
@response_worker.prepare
|
152
|
+
2.times { @response_worker.work_one_job }
|
153
|
+
end
|
154
|
+
assert_equal 4, $worker_test_count
|
155
|
+
assert_equal false, $worker_success
|
156
|
+
end # retry, bury
|
157
|
+
|
158
|
+
it "should support retrying jobs and succeeds" do
|
159
|
+
Backburner.configure { |config| config.max_job_retries = 2; config.retry_delay = 0 }
|
160
|
+
@worker = @worker_class.new('bar.foo.5')
|
161
|
+
@worker_class.enqueue TestRetryJobForking, ["bam", "foo"], :queue => 'bar.foo.5'
|
162
|
+
@worker.prepare
|
163
|
+
silenced(4) do
|
164
|
+
3.times do
|
165
|
+
$worker_test_count += 1
|
166
|
+
@worker.fork_one_job
|
167
|
+
end
|
168
|
+
@templogger.wait_for_match(/Completed TestRetryJobFork/m)
|
169
|
+
@response_worker.prepare
|
170
|
+
3.times { @response_worker.work_one_job }
|
171
|
+
end
|
172
|
+
assert_equal 6, $worker_test_count
|
173
|
+
assert_equal true, $worker_success
|
174
|
+
end # retrying, succeeds
|
175
|
+
|
176
|
+
end # practical tests
|
177
|
+
|
178
|
+
|
179
|
+
end
|
@@ -50,6 +50,10 @@ describe "Backburner::Workers::ThreadsOnFork module" do
|
|
50
50
|
end
|
51
51
|
|
52
52
|
describe "for prepare method" do
|
53
|
+
before do
|
54
|
+
Backburner.configure { |config| config.logger = false }
|
55
|
+
end
|
56
|
+
|
53
57
|
it "should watch specified tubes" do
|
54
58
|
worker = @worker_class.new(["foo", "bar"])
|
55
59
|
out = capture_stdout { worker.prepare }
|
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.3.
|
4
|
+
version: 0.3.1
|
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-
|
12
|
+
date: 2012-12-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: beaneater
|
@@ -131,6 +131,7 @@ files:
|
|
131
131
|
- lib/backburner/tasks.rb
|
132
132
|
- lib/backburner/version.rb
|
133
133
|
- lib/backburner/worker.rb
|
134
|
+
- lib/backburner/workers/forking.rb
|
134
135
|
- lib/backburner/workers/simple.rb
|
135
136
|
- lib/backburner/workers/threads_on_fork.rb
|
136
137
|
- test/async_proxy_test.rb
|
@@ -138,6 +139,7 @@ files:
|
|
138
139
|
- test/connection_test.rb
|
139
140
|
- test/fixtures/hooked.rb
|
140
141
|
- test/fixtures/test_fork_jobs.rb
|
142
|
+
- test/fixtures/test_forking_jobs.rb
|
141
143
|
- test/fixtures/test_jobs.rb
|
142
144
|
- test/helpers/templogger.rb
|
143
145
|
- test/helpers_test.rb
|
@@ -148,8 +150,9 @@ files:
|
|
148
150
|
- test/queue_test.rb
|
149
151
|
- test/test_helper.rb
|
150
152
|
- test/worker_test.rb
|
153
|
+
- test/workers/forking_worker_test.rb
|
151
154
|
- test/workers/simple_worker_test.rb
|
152
|
-
- test/workers/
|
155
|
+
- test/workers/threads_on_fork_worker_test.rb
|
153
156
|
homepage: http://github.com/nesquena/backburner
|
154
157
|
licenses: []
|
155
158
|
post_install_message:
|
@@ -180,6 +183,7 @@ test_files:
|
|
180
183
|
- test/connection_test.rb
|
181
184
|
- test/fixtures/hooked.rb
|
182
185
|
- test/fixtures/test_fork_jobs.rb
|
186
|
+
- test/fixtures/test_forking_jobs.rb
|
183
187
|
- test/fixtures/test_jobs.rb
|
184
188
|
- test/helpers/templogger.rb
|
185
189
|
- test/helpers_test.rb
|
@@ -190,6 +194,6 @@ test_files:
|
|
190
194
|
- test/queue_test.rb
|
191
195
|
- test/test_helper.rb
|
192
196
|
- test/worker_test.rb
|
197
|
+
- test/workers/forking_worker_test.rb
|
193
198
|
- test/workers/simple_worker_test.rb
|
194
|
-
- test/workers/
|
195
|
-
has_rdoc:
|
199
|
+
- test/workers/threads_on_fork_worker_test.rb
|