resque 1.21.0 → 1.22.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of resque might be problematic. Click here for more details.
- data/HISTORY.md +6 -0
- data/lib/resque/errors.rb +3 -0
- data/lib/resque/job.rb +3 -1
- data/lib/resque/tasks.rb +2 -0
- data/lib/resque/version.rb +1 -1
- data/lib/resque/worker.rb +56 -3
- data/test/dump.rdb +0 -0
- data/test/job_hooks_test.rb +41 -0
- data/test/test_helper.rb +21 -7
- data/test/worker_test.rb +149 -1
- metadata +6 -5
data/HISTORY.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## 1.22.0 (2012-08-21)
|
2
|
+
|
3
|
+
* unregister signal handlers in child process when ENV["TERM_CHILD"] is set (@dylanasmith, #621)
|
4
|
+
* new signal handling for TERM. See http://hone.heroku.com/resque/2012/08/21/resque-signals.html. (@wuputah, @yaaule, #638)
|
5
|
+
* supports calling perform hooks when using Resque.inline (@jonhyman, #506)
|
6
|
+
|
1
7
|
## 1.21.0 (2012-07-02)
|
2
8
|
|
3
9
|
* Add a flag to make sure failure hooks are only ran once (jakemack, #546)
|
data/lib/resque/errors.rb
CHANGED
data/lib/resque/job.rb
CHANGED
@@ -44,7 +44,9 @@ module Resque
|
|
44
44
|
Resque.validate(klass, queue)
|
45
45
|
|
46
46
|
if Resque.inline?
|
47
|
-
|
47
|
+
# Instantiating a Resque::Job and calling perform on it so callbacks run
|
48
|
+
# decode(encode(args)) to ensure that args are normalized in the same manner as a non-inline job
|
49
|
+
new(:inline, {'class' => klass, 'args' => decode(encode(args))}).perform
|
48
50
|
else
|
49
51
|
Resque.push(queue, :class => klass.to_s, :args => args)
|
50
52
|
end
|
data/lib/resque/tasks.rb
CHANGED
@@ -14,6 +14,8 @@ namespace :resque do
|
|
14
14
|
worker = Resque::Worker.new(*queues)
|
15
15
|
worker.verbose = ENV['LOGGING'] || ENV['VERBOSE']
|
16
16
|
worker.very_verbose = ENV['VVERBOSE']
|
17
|
+
worker.term_timeout = ENV['RESQUE_TERM_TIMEOUT'] || 4.0
|
18
|
+
worker.term_child = ENV['TERM_CHILD']
|
17
19
|
rescue Resque::NoQueueError
|
18
20
|
abort "set QUEUE env var, e.g. $ QUEUE=critical,high rake resque:work"
|
19
21
|
end
|
data/lib/resque/version.rb
CHANGED
data/lib/resque/worker.rb
CHANGED
@@ -20,6 +20,11 @@ module Resque
|
|
20
20
|
# Automatically set if a fork(2) fails.
|
21
21
|
attr_accessor :cant_fork
|
22
22
|
|
23
|
+
attr_accessor :term_timeout
|
24
|
+
|
25
|
+
# decide whether to use new_kill_child logic
|
26
|
+
attr_accessor :term_child
|
27
|
+
|
23
28
|
attr_writer :to_s
|
24
29
|
|
25
30
|
# Returns an array of all worker objects.
|
@@ -137,8 +142,13 @@ module Resque
|
|
137
142
|
if @child = fork
|
138
143
|
srand # Reseeding
|
139
144
|
procline "Forked #{@child} at #{Time.now.to_i}"
|
140
|
-
|
145
|
+
begin
|
146
|
+
Process.waitpid(@child)
|
147
|
+
rescue SystemCallError
|
148
|
+
nil
|
149
|
+
end
|
141
150
|
else
|
151
|
+
unregister_signal_handlers if !@cant_fork && term_child
|
142
152
|
procline "Processing #{job.queue} since #{Time.now.to_i}"
|
143
153
|
redis.client.reconnect # Don't share connection with parent
|
144
154
|
perform(job, &block)
|
@@ -238,6 +248,7 @@ module Resque
|
|
238
248
|
|
239
249
|
# Runs all the methods needed when a worker begins its lifecycle.
|
240
250
|
def startup
|
251
|
+
warn "WARNING: This way of doing signal handling is now deprecated. Please see http://hone.heroku.com/resque/2012/08/21/resque-signals.html for more info." unless term_child
|
241
252
|
enable_gc_optimizations
|
242
253
|
register_signal_handlers
|
243
254
|
prune_dead_workers
|
@@ -271,7 +282,11 @@ module Resque
|
|
271
282
|
|
272
283
|
begin
|
273
284
|
trap('QUIT') { shutdown }
|
274
|
-
|
285
|
+
if term_child
|
286
|
+
trap('USR1') { new_kill_child }
|
287
|
+
else
|
288
|
+
trap('USR1') { kill_child }
|
289
|
+
end
|
275
290
|
trap('USR2') { pause_processing }
|
276
291
|
trap('CONT') { unpause_processing }
|
277
292
|
rescue ArgumentError
|
@@ -281,6 +296,18 @@ module Resque
|
|
281
296
|
log! "Registered signals"
|
282
297
|
end
|
283
298
|
|
299
|
+
def unregister_signal_handlers
|
300
|
+
trap('TERM') { raise TermException.new("SIGTERM") }
|
301
|
+
trap('INT', 'DEFAULT')
|
302
|
+
|
303
|
+
begin
|
304
|
+
trap('QUIT', 'DEFAULT')
|
305
|
+
trap('USR1', 'DEFAULT')
|
306
|
+
trap('USR2', 'DEFAULT')
|
307
|
+
rescue ArgumentError
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
284
311
|
# Schedule this worker for shutdown. Will finish processing the
|
285
312
|
# current job.
|
286
313
|
def shutdown
|
@@ -291,7 +318,11 @@ module Resque
|
|
291
318
|
# Kill the child and shutdown immediately.
|
292
319
|
def shutdown!
|
293
320
|
shutdown
|
294
|
-
|
321
|
+
if term_child
|
322
|
+
new_kill_child
|
323
|
+
else
|
324
|
+
kill_child
|
325
|
+
end
|
295
326
|
end
|
296
327
|
|
297
328
|
# Should this worker shutdown as soon as current job is finished?
|
@@ -313,6 +344,28 @@ module Resque
|
|
313
344
|
end
|
314
345
|
end
|
315
346
|
|
347
|
+
# Kills the forked child immediately with minimal remorse. The job it
|
348
|
+
# is processing will not be completed. Send the child a TERM signal,
|
349
|
+
# wait 5 seconds, and then a KILL signal if it has not quit
|
350
|
+
def new_kill_child
|
351
|
+
if @child
|
352
|
+
unless Process.waitpid(@child, Process::WNOHANG)
|
353
|
+
log! "Sending TERM signal to child #{@child}"
|
354
|
+
Process.kill("TERM", @child)
|
355
|
+
(term_timeout.to_f * 10).round.times do |i|
|
356
|
+
sleep(0.1)
|
357
|
+
return if Process.waitpid(@child, Process::WNOHANG)
|
358
|
+
end
|
359
|
+
log! "Sending KILL signal to child #{@child}"
|
360
|
+
Process.kill("KILL", @child)
|
361
|
+
else
|
362
|
+
log! "Child #{@child} already quit."
|
363
|
+
end
|
364
|
+
end
|
365
|
+
rescue SystemCallError
|
366
|
+
log! "Child #{@child} already quit and reaped."
|
367
|
+
end
|
368
|
+
|
316
369
|
# are we paused?
|
317
370
|
def paused?
|
318
371
|
@paused
|
data/test/dump.rdb
ADDED
Binary file
|
data/test/job_hooks_test.rb
CHANGED
@@ -420,4 +420,45 @@ context "Resque::Job all hooks" do
|
|
420
420
|
"oh no"
|
421
421
|
]
|
422
422
|
end
|
423
|
+
|
424
|
+
class ::CallbacksInline
|
425
|
+
@queue = :callbacks_inline
|
426
|
+
|
427
|
+
def self.before_perform_record_history(history, count)
|
428
|
+
history << :before_perform
|
429
|
+
count['count'] += 1
|
430
|
+
end
|
431
|
+
|
432
|
+
def self.after_perform_record_history(history, count)
|
433
|
+
history << :after_perform
|
434
|
+
count['count'] += 1
|
435
|
+
end
|
436
|
+
|
437
|
+
def self.around_perform_record_history(history, count)
|
438
|
+
history << :start_around_perform
|
439
|
+
count['count'] += 1
|
440
|
+
yield
|
441
|
+
history << :finish_around_perform
|
442
|
+
count['count'] += 1
|
443
|
+
end
|
444
|
+
|
445
|
+
def self.perform(history, count)
|
446
|
+
history << :perform
|
447
|
+
$history = history
|
448
|
+
$count = count
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
test "it runs callbacks when inline is true" do
|
453
|
+
begin
|
454
|
+
Resque.inline = true
|
455
|
+
# Sending down two parameters that can be passed and updated by reference
|
456
|
+
result = Resque.enqueue(CallbacksInline, [], {'count' => 0})
|
457
|
+
assert_equal true, result, "perform returned true"
|
458
|
+
assert_equal $history, [:before_perform, :start_around_perform, :perform, :finish_around_perform, :after_perform]
|
459
|
+
assert_equal 4, $count['count']
|
460
|
+
ensure
|
461
|
+
Resque.inline = false
|
462
|
+
end
|
463
|
+
end
|
423
464
|
end
|
data/test/test_helper.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
|
3
|
-
dir = File.dirname(File.expand_path(__FILE__))
|
4
|
-
$LOAD_PATH.unshift dir + '/../lib'
|
3
|
+
$dir = File.dirname(File.expand_path(__FILE__))
|
4
|
+
$LOAD_PATH.unshift $dir + '/../lib'
|
5
5
|
$TESTING = true
|
6
6
|
require 'test/unit'
|
7
7
|
|
@@ -42,21 +42,21 @@ at_exit do
|
|
42
42
|
processes = `ps -A -o pid,command | grep [r]edis-test`.split("\n")
|
43
43
|
pids = processes.map { |process| process.split(" ")[0] }
|
44
44
|
puts "Killing test redis server..."
|
45
|
-
|
46
|
-
|
45
|
+
pids.each { |pid| Process.kill("TERM", pid.to_i) }
|
46
|
+
system("rm -f #{$dir}/dump.rdb #{$dir}/dump-cluster.rdb")
|
47
47
|
exit exit_code
|
48
48
|
end
|
49
49
|
|
50
50
|
if ENV.key? 'RESQUE_DISTRIBUTED'
|
51
51
|
require 'redis/distributed'
|
52
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`
|
53
|
+
`redis-server #{$dir}/redis-test.conf`
|
54
|
+
`redis-server #{$dir}/redis-test-cluster.conf`
|
55
55
|
r = Redis::Distributed.new(['redis://localhost:9736', 'redis://localhost:9737'])
|
56
56
|
Resque.redis = Redis::Namespace.new :resque, :redis => r
|
57
57
|
else
|
58
58
|
puts "Starting redis for testing at localhost:9736..."
|
59
|
-
`redis-server #{dir}/redis-test.conf`
|
59
|
+
`redis-server #{$dir}/redis-test.conf`
|
60
60
|
Resque.redis = 'localhost:9736'
|
61
61
|
end
|
62
62
|
|
@@ -144,6 +144,8 @@ ensure
|
|
144
144
|
Resque::Failure.backend = previous_backend
|
145
145
|
end
|
146
146
|
|
147
|
+
require 'time'
|
148
|
+
|
147
149
|
class Time
|
148
150
|
# Thanks, Timecop
|
149
151
|
class << self
|
@@ -158,3 +160,15 @@ class Time
|
|
158
160
|
|
159
161
|
self.fake_time = nil
|
160
162
|
end
|
163
|
+
|
164
|
+
def capture_stderr
|
165
|
+
# The output stream must be an IO-like object. In this case we capture it in
|
166
|
+
# an in-memory IO object so we can return the string value. You can assign any
|
167
|
+
# IO object here.
|
168
|
+
previous_stderr, $stderr = $stderr, StringIO.new
|
169
|
+
yield
|
170
|
+
$stderr.string
|
171
|
+
ensure
|
172
|
+
# Restore the previous value of stderr (typically equal to STDERR).
|
173
|
+
$stderr = previous_stderr
|
174
|
+
end
|
data/test/worker_test.rb
CHANGED
@@ -286,7 +286,7 @@ context "Resque::Worker" do
|
|
286
286
|
test "knows when it started" do
|
287
287
|
time = Time.now
|
288
288
|
@worker.work(0) do
|
289
|
-
|
289
|
+
assert Time.parse(@worker.started) - time < 0.1
|
290
290
|
end
|
291
291
|
end
|
292
292
|
|
@@ -437,4 +437,152 @@ context "Resque::Worker" do
|
|
437
437
|
@worker.work(0)
|
438
438
|
assert_not_equal original_connection, Resque.redis.client.connection.instance_variable_get("@sock")
|
439
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
|
587
|
+
end
|
440
588
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resque
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 79
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
8
|
+
- 22
|
9
9
|
- 0
|
10
|
-
version: 1.
|
10
|
+
version: 1.22.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Chris Wanstrath
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2012-
|
19
|
+
date: 2012-08-21 00:00:00 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
22
|
name: redis-namespace
|
@@ -151,6 +151,7 @@ files:
|
|
151
151
|
- test/test_helper.rb
|
152
152
|
- test/hoptoad_test.rb
|
153
153
|
- test/resque_test.rb
|
154
|
+
- test/dump.rdb
|
154
155
|
- test/job_plugins_test.rb
|
155
156
|
homepage: http://github.com/defunkt/resque
|
156
157
|
licenses: []
|
@@ -181,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
181
182
|
requirements: []
|
182
183
|
|
183
184
|
rubyforge_project:
|
184
|
-
rubygems_version: 1.8.
|
185
|
+
rubygems_version: 1.8.24
|
185
186
|
signing_key:
|
186
187
|
specification_version: 3
|
187
188
|
summary: Resque is a Redis-backed queueing system.
|