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