resque-loner 1.2.1 → 1.3.0
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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +92 -0
- data/.simplecov +1 -0
- data/.travis.yml +12 -0
- data/CHANGELOG.markdown +11 -0
- data/Gemfile +4 -5
- data/README.markdown +4 -1
- data/Rakefile +13 -41
- data/lib/resque-ext/job.rb +9 -9
- data/lib/resque-ext/resque.rb +4 -9
- data/lib/resque-loner.rb +2 -1
- data/lib/resque-loner/helpers.rb +22 -9
- data/lib/resque-loner/legacy_helpers.rb +91 -0
- data/lib/resque-loner/unique_job.rb +23 -14
- data/lib/resque-loner/version.rb +1 -1
- data/resque-loner.gemspec +41 -32
- data/spec/loner_spec.rb +73 -51
- data/spec/spec_helper.rb +19 -3
- data/spec/support/redis_instance.rb +133 -0
- data/test/airbrake_test.rb +27 -0
- data/test/dump.rdb +0 -0
- data/test/job_hooks_test.rb +191 -52
- data/test/job_plugins_test.rb +33 -31
- data/test/plugin_test.rb +38 -26
- data/{spec/redis-test.conf → test/redis-test-cluster.conf} +4 -4
- data/test/resque-web_test.rb +23 -17
- data/test/resque_failure_redis_test.rb +23 -0
- data/test/resque_test.rb +56 -37
- data/test/test_helper.rb +58 -29
- data/test/worker_test.rb +311 -55
- metadata +157 -58
- data/init.rb +0 -1
- data/rails/init.rb +0 -1
- data/test/hoptoad_test.rb +0 -25
data/test/test_helper.rb
CHANGED
@@ -1,37 +1,37 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'bundler'
|
3
|
-
Bundler.setup(:default, :test)
|
4
|
-
Bundler.require(:default, :test)
|
2
|
+
require 'bundler/setup'
|
5
3
|
|
6
|
-
dir = File.dirname(File.expand_path(__FILE__))
|
7
|
-
$LOAD_PATH.unshift dir + '/../lib'
|
4
|
+
$dir = File.dirname(File.expand_path(__FILE__))
|
5
|
+
$LOAD_PATH.unshift $dir + '/../lib'
|
8
6
|
$TESTING = true
|
9
7
|
require 'test/unit'
|
10
8
|
|
9
|
+
require 'redis/namespace'
|
10
|
+
require 'resque'
|
11
|
+
require 'resque-loner'
|
12
|
+
|
11
13
|
begin
|
12
14
|
require 'leftright'
|
13
15
|
rescue LoadError
|
14
16
|
end
|
15
17
|
|
16
|
-
|
17
18
|
#
|
18
19
|
# make sure we can run redis
|
19
20
|
#
|
20
21
|
|
21
|
-
if !system(
|
22
|
+
if !system('which redis-server')
|
22
23
|
puts '', "** can't find `redis-server` in your path"
|
23
|
-
puts
|
24
|
+
puts '** try running `sudo rake install`'
|
24
25
|
abort ''
|
25
26
|
end
|
26
27
|
|
27
|
-
|
28
28
|
#
|
29
29
|
# start our own redis when the tests start,
|
30
30
|
# kill it when they end
|
31
31
|
#
|
32
32
|
|
33
33
|
at_exit do
|
34
|
-
next if
|
34
|
+
next if $ERROR_INFO
|
35
35
|
|
36
36
|
if defined?(MiniTest)
|
37
37
|
exit_code = MiniTest::Unit.new.run(ARGV)
|
@@ -39,17 +39,26 @@ at_exit do
|
|
39
39
|
exit_code = Test::Unit::AutoRunner.run
|
40
40
|
end
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
Process.kill(
|
42
|
+
processes = `ps -A -o pid,command | grep [r]edis-test`.split("\n")
|
43
|
+
pids = processes.map { |process| process.split(' ')[0] }
|
44
|
+
puts 'Killing test redis server...'
|
45
|
+
pids.each { |pid| Process.kill('TERM', pid.to_i) }
|
46
|
+
system("rm -f #{$dir}/dump.rdb #{$dir}/dump-cluster.rdb")
|
46
47
|
exit exit_code
|
47
48
|
end
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
50
|
+
if ENV.key? 'RESQUE_DISTRIBUTED'
|
51
|
+
require 'redis/distributed'
|
52
|
+
puts 'Starting redis for testing at localhost:9736 and localhost:9737...'
|
53
|
+
`redis-server #{$dir}/redis-test.conf`
|
54
|
+
`redis-server #{$dir}/redis-test-cluster.conf`
|
55
|
+
r = Redis::Distributed.new(['redis://localhost:9736', 'redis://localhost:9737'])
|
56
|
+
Resque.redis = Redis::Namespace.new :resque, redis: r
|
57
|
+
else
|
58
|
+
puts 'Starting redis for testing at localhost:9736...'
|
59
|
+
`redis-server #{$dir}/redis-test.conf`
|
60
|
+
Resque.redis = 'localhost:9736'
|
61
|
+
end
|
53
62
|
|
54
63
|
##
|
55
64
|
# test/spec/mini 3
|
@@ -61,13 +70,17 @@ def context(*args, &block)
|
|
61
70
|
require 'test/unit'
|
62
71
|
klass = Class.new(defined?(ActiveSupport::TestCase) ? ActiveSupport::TestCase : Test::Unit::TestCase) do
|
63
72
|
def self.test(name, &block)
|
64
|
-
define_method("test_#{name.gsub(/\W/,'_')}", &block) if block
|
73
|
+
define_method("test_#{name.gsub(/\W/, '_')}", &block) if block
|
65
74
|
end
|
66
75
|
def self.xtest(*args) end
|
67
|
-
def self.setup(&block)
|
68
|
-
|
76
|
+
def self.setup(&block)
|
77
|
+
define_method(:setup, &block)
|
78
|
+
end
|
79
|
+
def self.teardown(&block)
|
80
|
+
define_method(:teardown, &block)
|
81
|
+
end
|
69
82
|
end
|
70
|
-
(class << klass; self end).send(:define_method, :name) { name.gsub(/\W/,'_') }
|
83
|
+
(class << klass; self end).send(:define_method, :name) { name.gsub(/\W/, '_') }
|
71
84
|
klass.class_eval &block
|
72
85
|
# XXX: In 1.8.x, not all tests will run unless anonymous classes are kept in scope.
|
73
86
|
($test_classes ||= []) << klass
|
@@ -104,7 +117,7 @@ end
|
|
104
117
|
|
105
118
|
class BadJob
|
106
119
|
def self.perform
|
107
|
-
|
120
|
+
fail 'Bad job!'
|
108
121
|
end
|
109
122
|
end
|
110
123
|
|
@@ -116,13 +129,13 @@ end
|
|
116
129
|
|
117
130
|
class BadJobWithSyntaxError
|
118
131
|
def self.perform
|
119
|
-
|
132
|
+
fail SyntaxError, 'Extra Bad job!'
|
120
133
|
end
|
121
134
|
end
|
122
135
|
|
123
136
|
class BadFailureBackend < Resque::Failure::Base
|
124
137
|
def save
|
125
|
-
|
138
|
+
fail Exception.new('Failure backend error')
|
126
139
|
end
|
127
140
|
end
|
128
141
|
|
@@ -134,15 +147,31 @@ ensure
|
|
134
147
|
Resque::Failure.backend = previous_backend
|
135
148
|
end
|
136
149
|
|
150
|
+
require 'time'
|
151
|
+
|
137
152
|
class Time
|
138
153
|
# Thanks, Timecop
|
139
154
|
class << self
|
155
|
+
attr_accessor :fake_time
|
156
|
+
|
140
157
|
alias_method :now_without_mock_time, :now
|
141
158
|
|
142
|
-
def
|
143
|
-
|
159
|
+
def now
|
160
|
+
fake_time || now_without_mock_time
|
144
161
|
end
|
145
|
-
|
146
|
-
alias_method :now, :now_with_mock_time
|
147
162
|
end
|
163
|
+
|
164
|
+
self.fake_time = nil
|
165
|
+
end
|
166
|
+
|
167
|
+
def capture_stderr
|
168
|
+
# The output stream must be an IO-like object. In this case we capture it in
|
169
|
+
# an in-memory IO object so we can return the string value. You can assign any
|
170
|
+
# IO object here.
|
171
|
+
previous_stderr, $stderr = $stderr, StringIO.new
|
172
|
+
yield
|
173
|
+
$stderr.string
|
174
|
+
ensure
|
175
|
+
# Restore the previous value of stderr (typically equal to STDERR).
|
176
|
+
$stderr = previous_stderr
|
148
177
|
end
|
data/test/worker_test.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
context
|
3
|
+
context 'Resque::Worker' do
|
4
4
|
setup do
|
5
5
|
Resque.redis.flushall
|
6
6
|
|
@@ -12,34 +12,75 @@ context "Resque::Worker" do
|
|
12
12
|
Resque::Job.create(:jobs, SomeJob, 20, '/tmp')
|
13
13
|
end
|
14
14
|
|
15
|
-
test
|
15
|
+
test 'can fail jobs' do
|
16
16
|
Resque::Job.create(:jobs, BadJob)
|
17
17
|
@worker.work(0)
|
18
18
|
assert_equal 1, Resque::Failure.count
|
19
19
|
end
|
20
20
|
|
21
|
-
test
|
21
|
+
test 'failed jobs report exception and message' do
|
22
22
|
Resque::Job.create(:jobs, BadJobWithSyntaxError)
|
23
23
|
@worker.work(0)
|
24
24
|
assert_equal('SyntaxError', Resque::Failure.all['exception'])
|
25
25
|
assert_equal('Extra Bad job!', Resque::Failure.all['error'])
|
26
26
|
end
|
27
27
|
|
28
|
-
test
|
28
|
+
test 'does not allow exceptions from failure backend to escape' do
|
29
29
|
job = Resque::Job.new(:jobs, {})
|
30
30
|
with_failure_backend BadFailureBackend do
|
31
31
|
@worker.perform job
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
test
|
36
|
-
job = Resque::Job.new(:jobs,
|
35
|
+
test 'fails uncompleted jobs on exit' do
|
36
|
+
job = Resque::Job.new(:jobs, 'class' => 'GoodJob', 'args' => 'blah')
|
37
37
|
@worker.working_on(job)
|
38
38
|
@worker.unregister_worker
|
39
39
|
assert_equal 1, Resque::Failure.count
|
40
40
|
end
|
41
41
|
|
42
|
-
|
42
|
+
class ::SimpleJobWithFailureHandling
|
43
|
+
def self.on_failure_record_failure(exception, *job_args)
|
44
|
+
@@exception = exception
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.exception
|
48
|
+
@@exception
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
test 'fails uncompleted jobs on exit, and calls failure hook' do
|
53
|
+
job = Resque::Job.new(:jobs, 'class' => 'SimpleJobWithFailureHandling', 'args' => '')
|
54
|
+
@worker.working_on(job)
|
55
|
+
@worker.unregister_worker
|
56
|
+
assert_equal 1, Resque::Failure.count
|
57
|
+
assert(SimpleJobWithFailureHandling.exception.kind_of?(Resque::DirtyExit))
|
58
|
+
end
|
59
|
+
|
60
|
+
class ::SimpleFailingJob
|
61
|
+
@@exception_count = 0
|
62
|
+
|
63
|
+
def self.on_failure_record_failure(exception, *job_args)
|
64
|
+
@@exception_count += 1
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.exception_count
|
68
|
+
@@exception_count
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.perform
|
72
|
+
fail Exception.new
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
test 'only calls failure hook once on exception' do
|
77
|
+
job = Resque::Job.new(:jobs, 'class' => 'SimpleFailingJob', 'args' => '')
|
78
|
+
@worker.perform(job)
|
79
|
+
assert_equal 1, Resque::Failure.count
|
80
|
+
assert_equal 1, SimpleFailingJob.exception_count
|
81
|
+
end
|
82
|
+
|
83
|
+
test 'can peek at failed jobs' do
|
43
84
|
10.times { Resque::Job.create(:jobs, BadJob) }
|
44
85
|
@worker.work(0)
|
45
86
|
assert_equal 10, Resque::Failure.count
|
@@ -47,7 +88,7 @@ context "Resque::Worker" do
|
|
47
88
|
assert_equal 10, Resque::Failure.all(0, 20).size
|
48
89
|
end
|
49
90
|
|
50
|
-
test
|
91
|
+
test 'can clear failed jobs' do
|
51
92
|
Resque::Job.create(:jobs, BadJob)
|
52
93
|
@worker.work(0)
|
53
94
|
assert_equal 1, Resque::Failure.count
|
@@ -55,7 +96,7 @@ context "Resque::Worker" do
|
|
55
96
|
assert_equal 0, Resque::Failure.count
|
56
97
|
end
|
57
98
|
|
58
|
-
test
|
99
|
+
test 'catches exceptional jobs' do
|
59
100
|
Resque::Job.create(:jobs, BadJob)
|
60
101
|
Resque::Job.create(:jobs, BadJob)
|
61
102
|
@worker.process
|
@@ -64,13 +105,13 @@ context "Resque::Worker" do
|
|
64
105
|
assert_equal 2, Resque::Failure.count
|
65
106
|
end
|
66
107
|
|
67
|
-
test
|
68
|
-
queues =
|
108
|
+
test 'strips whitespace from queue names' do
|
109
|
+
queues = 'critical, high, low'.split(',')
|
69
110
|
worker = Resque::Worker.new(*queues)
|
70
111
|
assert_equal %w( critical high low ), worker.queues
|
71
112
|
end
|
72
113
|
|
73
|
-
test
|
114
|
+
test 'can work on multiple queues' do
|
74
115
|
Resque::Job.create(:high, GoodJob)
|
75
116
|
Resque::Job.create(:critical, GoodJob)
|
76
117
|
|
@@ -84,25 +125,55 @@ context "Resque::Worker" do
|
|
84
125
|
assert_equal 0, Resque.size(:high)
|
85
126
|
end
|
86
127
|
|
87
|
-
test
|
128
|
+
test 'can work on all queues' do
|
129
|
+
Resque::Job.create(:high, GoodJob)
|
130
|
+
Resque::Job.create(:critical, GoodJob)
|
131
|
+
Resque::Job.create(:blahblah, GoodJob)
|
132
|
+
|
133
|
+
worker = Resque::Worker.new('*')
|
134
|
+
|
135
|
+
worker.work(0)
|
136
|
+
assert_equal 0, Resque.size(:high)
|
137
|
+
assert_equal 0, Resque.size(:critical)
|
138
|
+
assert_equal 0, Resque.size(:blahblah)
|
139
|
+
end
|
140
|
+
|
141
|
+
test 'can work with wildcard at the end of the list' do
|
88
142
|
Resque::Job.create(:high, GoodJob)
|
89
143
|
Resque::Job.create(:critical, GoodJob)
|
90
144
|
Resque::Job.create(:blahblah, GoodJob)
|
145
|
+
Resque::Job.create(:beer, GoodJob)
|
91
146
|
|
92
|
-
worker = Resque::Worker.new(
|
147
|
+
worker = Resque::Worker.new(:critical, :high, '*')
|
93
148
|
|
94
149
|
worker.work(0)
|
95
150
|
assert_equal 0, Resque.size(:high)
|
96
151
|
assert_equal 0, Resque.size(:critical)
|
97
152
|
assert_equal 0, Resque.size(:blahblah)
|
153
|
+
assert_equal 0, Resque.size(:beer)
|
98
154
|
end
|
99
155
|
|
100
|
-
test
|
156
|
+
test 'can work with wildcard at the middle of the list' do
|
101
157
|
Resque::Job.create(:high, GoodJob)
|
102
158
|
Resque::Job.create(:critical, GoodJob)
|
103
159
|
Resque::Job.create(:blahblah, GoodJob)
|
160
|
+
Resque::Job.create(:beer, GoodJob)
|
161
|
+
|
162
|
+
worker = Resque::Worker.new(:critical, '*', :high)
|
104
163
|
|
105
|
-
worker
|
164
|
+
worker.work(0)
|
165
|
+
assert_equal 0, Resque.size(:high)
|
166
|
+
assert_equal 0, Resque.size(:critical)
|
167
|
+
assert_equal 0, Resque.size(:blahblah)
|
168
|
+
assert_equal 0, Resque.size(:beer)
|
169
|
+
end
|
170
|
+
|
171
|
+
test 'processes * queues in alphabetical order' do
|
172
|
+
Resque::Job.create(:high, GoodJob)
|
173
|
+
Resque::Job.create(:critical, GoodJob)
|
174
|
+
Resque::Job.create(:blahblah, GoodJob)
|
175
|
+
|
176
|
+
worker = Resque::Worker.new('*')
|
106
177
|
processed_queues = []
|
107
178
|
|
108
179
|
worker.work(0) do |job|
|
@@ -112,17 +183,17 @@ context "Resque::Worker" do
|
|
112
183
|
assert_equal %w( jobs high critical blahblah ).sort, processed_queues
|
113
184
|
end
|
114
185
|
|
115
|
-
test
|
116
|
-
assert_equal "#{`hostname`.chomp}:#{
|
186
|
+
test 'has a unique id' do
|
187
|
+
assert_equal "#{`hostname`.chomp}:#{$PROCESS_ID}:jobs", @worker.to_s
|
117
188
|
end
|
118
189
|
|
119
|
-
test
|
190
|
+
test 'complains if no queues are given' do
|
120
191
|
assert_raise Resque::NoQueueError do
|
121
192
|
Resque::Worker.new
|
122
193
|
end
|
123
194
|
end
|
124
195
|
|
125
|
-
test
|
196
|
+
test 'fails if a job class has no `perform` method' do
|
126
197
|
worker = Resque::Worker.new(:perform_less)
|
127
198
|
Resque::Job.create(:perform_less, Object)
|
128
199
|
|
@@ -145,7 +216,7 @@ context "Resque::Worker" do
|
|
145
216
|
assert_equal [], Resque.workers
|
146
217
|
end
|
147
218
|
|
148
|
-
test
|
219
|
+
test 'removes worker with stringified id' do
|
149
220
|
@worker.work(0) do
|
150
221
|
worker_id = Resque.workers[0].to_s
|
151
222
|
Resque.remove_worker(worker_id)
|
@@ -153,38 +224,38 @@ context "Resque::Worker" do
|
|
153
224
|
end
|
154
225
|
end
|
155
226
|
|
156
|
-
test
|
227
|
+
test 'records what it is working on' do
|
157
228
|
@worker.work(0) do
|
158
229
|
task = @worker.job
|
159
|
-
assert_equal({
|
230
|
+
assert_equal({ 'args' => [20, '/tmp'], 'class' => 'SomeJob' }, task['payload'])
|
160
231
|
assert task['run_at']
|
161
232
|
assert_equal 'jobs', task['queue']
|
162
233
|
end
|
163
234
|
end
|
164
235
|
|
165
|
-
test
|
236
|
+
test 'clears its status when not working on anything' do
|
166
237
|
@worker.work(0)
|
167
238
|
assert_equal Hash.new, @worker.job
|
168
239
|
end
|
169
240
|
|
170
|
-
test
|
241
|
+
test 'knows when it is working' do
|
171
242
|
@worker.work(0) do
|
172
243
|
assert @worker.working?
|
173
244
|
end
|
174
245
|
end
|
175
246
|
|
176
|
-
test
|
247
|
+
test 'knows when it is idle' do
|
177
248
|
@worker.work(0)
|
178
249
|
assert @worker.idle?
|
179
250
|
end
|
180
251
|
|
181
|
-
test
|
252
|
+
test 'knows who is working' do
|
182
253
|
@worker.work(0) do
|
183
254
|
assert_equal [@worker], Resque.working
|
184
255
|
end
|
185
256
|
end
|
186
257
|
|
187
|
-
test
|
258
|
+
test 'keeps track of how many jobs it has processed' do
|
188
259
|
Resque::Job.create(:jobs, BadJob)
|
189
260
|
Resque::Job.create(:jobs, BadJob)
|
190
261
|
|
@@ -195,7 +266,7 @@ context "Resque::Worker" do
|
|
195
266
|
assert_equal 3, @worker.processed
|
196
267
|
end
|
197
268
|
|
198
|
-
test
|
269
|
+
test 'keeps track of how many failures it has seen' do
|
199
270
|
Resque::Job.create(:jobs, BadJob)
|
200
271
|
Resque::Job.create(:jobs, BadJob)
|
201
272
|
|
@@ -206,34 +277,34 @@ context "Resque::Worker" do
|
|
206
277
|
assert_equal 2, @worker.failed
|
207
278
|
end
|
208
279
|
|
209
|
-
test
|
280
|
+
test 'stats are erased when the worker goes away' do
|
210
281
|
@worker.work(0)
|
211
282
|
assert_equal 0, @worker.processed
|
212
283
|
assert_equal 0, @worker.failed
|
213
284
|
end
|
214
285
|
|
215
|
-
test
|
286
|
+
test 'knows when it started' do
|
216
287
|
time = Time.now
|
217
288
|
@worker.work(0) do
|
218
|
-
|
289
|
+
assert Time.parse(@worker.started) - time < 0.1
|
219
290
|
end
|
220
291
|
end
|
221
292
|
|
222
|
-
test
|
293
|
+
test 'knows whether it exists or not' do
|
223
294
|
@worker.work(0) do
|
224
295
|
assert Resque::Worker.exists?(@worker)
|
225
296
|
assert !Resque::Worker.exists?('blah-blah')
|
226
297
|
end
|
227
298
|
end
|
228
299
|
|
229
|
-
test
|
300
|
+
test 'sets $0 while working' do
|
230
301
|
@worker.work(0) do
|
231
302
|
ver = Resque::Version
|
232
|
-
assert_equal "resque-#{ver}: Processing jobs since #{Time.now.to_i}", $
|
303
|
+
assert_equal "resque-#{ver}: Processing jobs since #{Time.now.to_i}", $PROGRAM_NAME
|
233
304
|
end
|
234
305
|
end
|
235
306
|
|
236
|
-
test
|
307
|
+
test 'can be found' do
|
237
308
|
@worker.work(0) do
|
238
309
|
found = Resque::Worker.find(@worker.to_s)
|
239
310
|
assert_equal @worker.to_s, found.to_s
|
@@ -249,7 +320,7 @@ context "Resque::Worker" do
|
|
249
320
|
end
|
250
321
|
end
|
251
322
|
|
252
|
-
test
|
323
|
+
test 'cleans up dead worker info on start (crash recovery)' do
|
253
324
|
# first we fake out two dead workers
|
254
325
|
workerA = Resque::Worker.new(:jobs)
|
255
326
|
workerA.instance_variable_set(:@to_s, "#{`hostname`.chomp}:1:jobs")
|
@@ -267,15 +338,20 @@ context "Resque::Worker" do
|
|
267
338
|
end
|
268
339
|
end
|
269
340
|
|
270
|
-
test
|
341
|
+
test 'worker_pids returns pids' do
|
342
|
+
known_workers = @worker.worker_pids
|
343
|
+
assert !known_workers.empty?
|
344
|
+
end
|
345
|
+
|
346
|
+
test 'Processed jobs count' do
|
271
347
|
@worker.work(0)
|
272
348
|
assert_equal 1, Resque.info[:processed]
|
273
349
|
end
|
274
350
|
|
275
|
-
test
|
351
|
+
test 'Will call a before_first_fork hook only once' do
|
276
352
|
Resque.redis.flushall
|
277
353
|
$BEFORE_FORK_CALLED = 0
|
278
|
-
Resque.before_first_fork =
|
354
|
+
Resque.before_first_fork = proc { $BEFORE_FORK_CALLED += 1 }
|
279
355
|
workerA = Resque::Worker.new(:jobs)
|
280
356
|
Resque::Job.create(:jobs, SomeJob, 20, '/tmp')
|
281
357
|
|
@@ -289,10 +365,10 @@ context "Resque::Worker" do
|
|
289
365
|
# assert_equal 1, $BEFORE_FORK_CALLED
|
290
366
|
end
|
291
367
|
|
292
|
-
test
|
368
|
+
test 'Will call a before_fork hook before forking' do
|
293
369
|
Resque.redis.flushall
|
294
370
|
$BEFORE_FORK_CALLED = false
|
295
|
-
Resque.before_fork =
|
371
|
+
Resque.before_fork = proc { $BEFORE_FORK_CALLED = true }
|
296
372
|
workerA = Resque::Worker.new(:jobs)
|
297
373
|
|
298
374
|
assert !$BEFORE_FORK_CALLED
|
@@ -301,23 +377,29 @@ context "Resque::Worker" do
|
|
301
377
|
assert $BEFORE_FORK_CALLED
|
302
378
|
end
|
303
379
|
|
304
|
-
test
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
singleton.send :define_method, :puts, lambda { |thing| $last_puts = thing }
|
380
|
+
test 'very verbose works in the afternoon' do
|
381
|
+
begin
|
382
|
+
require 'time'
|
383
|
+
last_puts = ''
|
384
|
+
Time.fake_time = Time.parse('15:44:33 2011-03-02')
|
310
385
|
|
311
|
-
|
312
|
-
|
386
|
+
@worker.extend(Module.new do
|
387
|
+
define_method(:puts) { |thing| last_puts = thing }
|
388
|
+
end)
|
313
389
|
|
314
|
-
|
390
|
+
@worker.very_verbose = true
|
391
|
+
@worker.log('some log text')
|
392
|
+
|
393
|
+
assert_match /\*\* \[15:44:33 2011-03-02\] \d+: some log text/, last_puts
|
394
|
+
ensure
|
395
|
+
Time.fake_time = nil
|
396
|
+
end
|
315
397
|
end
|
316
398
|
|
317
|
-
test
|
399
|
+
test 'Will call an after_fork hook after forking' do
|
318
400
|
Resque.redis.flushall
|
319
401
|
$AFTER_FORK_CALLED = false
|
320
|
-
Resque.after_fork =
|
402
|
+
Resque.after_fork = proc { $AFTER_FORK_CALLED = true }
|
321
403
|
workerA = Resque::Worker.new(:jobs)
|
322
404
|
|
323
405
|
assert !$AFTER_FORK_CALLED
|
@@ -326,7 +408,181 @@ context "Resque::Worker" do
|
|
326
408
|
assert $AFTER_FORK_CALLED
|
327
409
|
end
|
328
410
|
|
329
|
-
test
|
330
|
-
assert_equal @worker.to_s.split(
|
411
|
+
test 'returns PID of running process' do
|
412
|
+
assert_equal @worker.to_s.split(':')[1].to_i, @worker.pid
|
413
|
+
end
|
414
|
+
|
415
|
+
test 'requeue failed queue' do
|
416
|
+
queue = 'good_job'
|
417
|
+
Resque::Failure.create(exception: Exception.new, worker: Resque::Worker.new(queue), queue: queue, payload: { 'class' => 'GoodJob' })
|
418
|
+
Resque::Failure.create(exception: Exception.new, worker: Resque::Worker.new(queue), queue: 'some_job', payload: { 'class' => 'SomeJob' })
|
419
|
+
Resque::Failure.requeue_queue(queue)
|
420
|
+
assert Resque::Failure.all(0).key?('retried_at')
|
421
|
+
assert !Resque::Failure.all(1).key?('retried_at')
|
422
|
+
end
|
423
|
+
|
424
|
+
test 'remove failed queue' do
|
425
|
+
queue = 'good_job'
|
426
|
+
queue2 = 'some_job'
|
427
|
+
Resque::Failure.create(exception: Exception.new, worker: Resque::Worker.new(queue), queue: queue, payload: { 'class' => 'GoodJob' })
|
428
|
+
Resque::Failure.create(exception: Exception.new, worker: Resque::Worker.new(queue2), queue: queue2, payload: { 'class' => 'SomeJob' })
|
429
|
+
Resque::Failure.create(exception: Exception.new, worker: Resque::Worker.new(queue), queue: queue, payload: { 'class' => 'GoodJob' })
|
430
|
+
Resque::Failure.remove_queue(queue)
|
431
|
+
assert_equal queue2, Resque::Failure.all(0)['queue']
|
432
|
+
assert_equal 1, Resque::Failure.count
|
433
|
+
end
|
434
|
+
|
435
|
+
test 'reconnects to redis after fork' do
|
436
|
+
original_connection = Resque.redis.client.connection.instance_variable_get('@sock')
|
437
|
+
@worker.work(0)
|
438
|
+
assert_not_equal original_connection, Resque.redis.client.connection.instance_variable_get('@sock')
|
439
|
+
end
|
440
|
+
|
441
|
+
if !defined?(RUBY_ENGINE) || defined?(RUBY_ENGINE) && RUBY_ENGINE != 'jruby'
|
442
|
+
test 'old signal handling is the default' do
|
443
|
+
rescue_time = nil
|
444
|
+
|
445
|
+
begin
|
446
|
+
class LongRunningJob
|
447
|
+
@queue = :long_running_job
|
448
|
+
|
449
|
+
def self.perform(run_time, rescue_time = nil)
|
450
|
+
Resque.redis.client.reconnect # get its own connection
|
451
|
+
Resque.redis.rpush('sigterm-test:start', Process.pid)
|
452
|
+
sleep run_time
|
453
|
+
Resque.redis.rpush('sigterm-test:result', 'Finished Normally')
|
454
|
+
rescue Resque::TermException => e
|
455
|
+
Resque.redis.rpush('sigterm-test:result', %Q(Caught SignalException: #{e.inspect}))
|
456
|
+
sleep rescue_time unless rescue_time.nil?
|
457
|
+
ensure
|
458
|
+
puts 'fuuuu'
|
459
|
+
Resque.redis.rpush('sigterm-test:final', 'exiting.')
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
Resque.enqueue(LongRunningJob, 5, rescue_time)
|
464
|
+
|
465
|
+
worker_pid = Kernel.fork do
|
466
|
+
# ensure we actually fork
|
467
|
+
$TESTING = false
|
468
|
+
# reconnect since we just forked
|
469
|
+
Resque.redis.client.reconnect
|
470
|
+
|
471
|
+
worker = Resque::Worker.new(:long_running_job)
|
472
|
+
|
473
|
+
worker.work(0)
|
474
|
+
exit!
|
475
|
+
end
|
476
|
+
|
477
|
+
# ensure the worker is started
|
478
|
+
start_status = Resque.redis.blpop('sigterm-test:start', 5)
|
479
|
+
assert_not_nil start_status
|
480
|
+
child_pid = start_status[1].to_i
|
481
|
+
assert_operator child_pid, :>, 0
|
482
|
+
|
483
|
+
# send signal to abort the worker
|
484
|
+
Process.kill('TERM', worker_pid)
|
485
|
+
Process.waitpid(worker_pid)
|
486
|
+
|
487
|
+
# wait to see how it all came down
|
488
|
+
result = Resque.redis.blpop('sigterm-test:result', 5)
|
489
|
+
assert_nil result
|
490
|
+
|
491
|
+
# ensure that the child pid is no longer running
|
492
|
+
child_still_running = !(`ps -p #{child_pid.to_s} -o pid=`).empty?
|
493
|
+
assert !child_still_running
|
494
|
+
ensure
|
495
|
+
remaining_keys = Resque.redis.keys('sigterm-test:*') || []
|
496
|
+
Resque.redis.del(*remaining_keys) unless remaining_keys.empty?
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
if !defined?(RUBY_ENGINE) || defined?(RUBY_ENGINE) && RUBY_ENGINE != 'jruby'
|
502
|
+
[SignalException, Resque::TermException].each do |exception|
|
503
|
+
{
|
504
|
+
'cleanup occurs in allotted time' => nil,
|
505
|
+
'cleanup takes too long' => 2
|
506
|
+
}.each do |scenario, rescue_time|
|
507
|
+
test "SIGTERM when #{scenario} while catching #{exception}" do
|
508
|
+
begin
|
509
|
+
eval("class LongRunningJob; @@exception = #{exception}; end")
|
510
|
+
class LongRunningJob
|
511
|
+
@queue = :long_running_job
|
512
|
+
|
513
|
+
def self.perform(run_time, rescue_time = nil)
|
514
|
+
Resque.redis.client.reconnect # get its own connection
|
515
|
+
Resque.redis.rpush('sigterm-test:start', Process.pid)
|
516
|
+
sleep run_time
|
517
|
+
Resque.redis.rpush('sigterm-test:result', 'Finished Normally')
|
518
|
+
rescue @@exception => e
|
519
|
+
Resque.redis.rpush('sigterm-test:result', %Q(Caught SignalException: #{e.inspect}))
|
520
|
+
sleep rescue_time unless rescue_time.nil?
|
521
|
+
ensure
|
522
|
+
Resque.redis.rpush('sigterm-test:final', 'exiting.')
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
Resque.enqueue(LongRunningJob, 5, rescue_time)
|
527
|
+
|
528
|
+
worker_pid = Kernel.fork do
|
529
|
+
# ensure we actually fork
|
530
|
+
$TESTING = false
|
531
|
+
# reconnect since we just forked
|
532
|
+
Resque.redis.client.reconnect
|
533
|
+
|
534
|
+
worker = Resque::Worker.new(:long_running_job)
|
535
|
+
worker.term_timeout = 1
|
536
|
+
worker.term_child = 1
|
537
|
+
|
538
|
+
worker.work(0)
|
539
|
+
exit!
|
540
|
+
end
|
541
|
+
|
542
|
+
# ensure the worker is started
|
543
|
+
start_status = Resque.redis.blpop('sigterm-test:start', 5)
|
544
|
+
assert_not_nil start_status
|
545
|
+
child_pid = start_status[1].to_i
|
546
|
+
assert_operator child_pid, :>, 0
|
547
|
+
|
548
|
+
# send signal to abort the worker
|
549
|
+
Process.kill('TERM', worker_pid)
|
550
|
+
Process.waitpid(worker_pid)
|
551
|
+
|
552
|
+
# wait to see how it all came down
|
553
|
+
result = Resque.redis.blpop('sigterm-test:result', 5)
|
554
|
+
assert_not_nil result
|
555
|
+
assert !result[1].start_with?('Finished Normally'), 'Job Finished normally. Sleep not long enough?'
|
556
|
+
assert result[1].start_with? 'Caught SignalException', 'Signal exception not raised in child.'
|
557
|
+
|
558
|
+
# ensure that the child pid is no longer running
|
559
|
+
child_still_running = !(`ps -p #{child_pid.to_s} -o pid=`).empty?
|
560
|
+
assert !child_still_running
|
561
|
+
|
562
|
+
# see if post-cleanup occurred. This should happen IFF the rescue_time is less than the term_timeout
|
563
|
+
post_cleanup_occurred = Resque.redis.lpop('sigterm-test:final')
|
564
|
+
assert post_cleanup_occurred, 'post cleanup did not occur. SIGKILL sent too early?' if rescue_time.nil?
|
565
|
+
assert !post_cleanup_occurred, 'post cleanup occurred. SIGKILL sent too late?' unless rescue_time.nil?
|
566
|
+
|
567
|
+
ensure
|
568
|
+
remaining_keys = Resque.redis.keys('sigterm-test:*') || []
|
569
|
+
Resque.redis.del(*remaining_keys) unless remaining_keys.empty?
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
test 'displays warning when not using term_child' do
|
576
|
+
stderr = capture_stderr { @worker.work(0) }
|
577
|
+
|
578
|
+
assert stderr.match(/^WARNING:/)
|
579
|
+
end
|
580
|
+
|
581
|
+
test 'it does not display warning when using term_child' do
|
582
|
+
@worker.term_child = '1'
|
583
|
+
stderr = capture_stderr { @worker.work(0) }
|
584
|
+
|
585
|
+
assert !stderr.match(/^WARNING:/)
|
586
|
+
end
|
331
587
|
end
|
332
588
|
end
|