resque 1.26.0 → 1.27.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.
- checksums.yaml +4 -4
- data/HISTORY.md +10 -2
- data/README.markdown +3 -3
- data/lib/resque.rb +34 -35
- data/lib/resque/data_store.rb +326 -0
- data/lib/resque/errors.rb +8 -1
- data/lib/resque/failure.rb +1 -1
- data/lib/resque/failure/airbrake.rb +1 -1
- data/lib/resque/failure/base.rb +4 -4
- data/lib/resque/failure/multiple.rb +4 -0
- data/lib/resque/failure/redis.rb +20 -9
- data/lib/resque/failure/redis_multi_queue.rb +17 -10
- data/lib/resque/job.rb +8 -4
- data/lib/resque/server.rb +1 -1
- data/lib/resque/server/helpers.rb +13 -1
- data/lib/resque/server/public/jquery-1.12.4.min.js +5 -0
- data/lib/resque/server/views/failed.erb +3 -3
- data/lib/resque/server/views/key_sets.erb +1 -3
- data/lib/resque/server/views/layout.erb +2 -2
- data/lib/resque/server/views/queues.erb +1 -1
- data/lib/resque/server/views/working.erb +3 -2
- data/lib/resque/stat.rb +5 -4
- data/lib/resque/tasks.rb +14 -7
- data/lib/resque/thread_signal.rb +45 -0
- data/lib/resque/version.rb +1 -1
- data/lib/resque/worker.rb +129 -112
- data/lib/tasks/redis.rake +1 -1
- metadata +60 -57
- data/lib/resque/server/public/jquery-1.3.2.min.js +0 -19
@@ -0,0 +1,45 @@
|
|
1
|
+
class Resque::ThreadSignal
|
2
|
+
if RUBY_VERSION <= "1.9"
|
3
|
+
def initialize
|
4
|
+
@signaled = false
|
5
|
+
end
|
6
|
+
|
7
|
+
def signal
|
8
|
+
@signaled = true
|
9
|
+
end
|
10
|
+
|
11
|
+
def wait_for_signal(timeout)
|
12
|
+
(10 * timeout).times do
|
13
|
+
sleep(0.1)
|
14
|
+
return true if @signaled
|
15
|
+
end
|
16
|
+
|
17
|
+
@signaled
|
18
|
+
end
|
19
|
+
|
20
|
+
else
|
21
|
+
def initialize
|
22
|
+
@mutex = Mutex.new
|
23
|
+
@signaled = false
|
24
|
+
@received = ConditionVariable.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def signal
|
28
|
+
@mutex.synchronize do
|
29
|
+
@signaled = true
|
30
|
+
@received.signal
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def wait_for_signal(timeout)
|
35
|
+
@mutex.synchronize do
|
36
|
+
unless @signaled
|
37
|
+
@received.wait(@mutex, timeout)
|
38
|
+
end
|
39
|
+
|
40
|
+
@signaled
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
data/lib/resque/version.rb
CHANGED
data/lib/resque/worker.rb
CHANGED
@@ -14,16 +14,25 @@ module Resque
|
|
14
14
|
extend Resque::Helpers
|
15
15
|
include Resque::Logging
|
16
16
|
|
17
|
-
|
17
|
+
@@all_heartbeat_threads = []
|
18
|
+
def self.kill_all_heartbeat_threads
|
19
|
+
@@all_heartbeat_threads.each(&:kill).each(&:join)
|
20
|
+
@@all_heartbeat_threads = []
|
21
|
+
end
|
18
22
|
|
19
23
|
def redis
|
20
24
|
Resque.redis
|
21
25
|
end
|
26
|
+
alias :data_store :redis
|
22
27
|
|
23
28
|
def self.redis
|
24
29
|
Resque.redis
|
25
30
|
end
|
26
31
|
|
32
|
+
def self.data_store
|
33
|
+
self.redis
|
34
|
+
end
|
35
|
+
|
27
36
|
# Given a Ruby object, returns a string suitable for storage in a
|
28
37
|
# queue.
|
29
38
|
def encode(object)
|
@@ -48,12 +57,14 @@ module Resque
|
|
48
57
|
# registered in the application. Otherwise, forked workers exit with `exit!`
|
49
58
|
attr_accessor :run_at_exit_hooks
|
50
59
|
|
60
|
+
attr_writer :fork_per_job
|
61
|
+
attr_writer :hostname
|
51
62
|
attr_writer :to_s
|
52
63
|
attr_writer :pid
|
53
64
|
|
54
65
|
# Returns an array of all worker objects.
|
55
66
|
def self.all
|
56
|
-
|
67
|
+
data_store.worker_ids.map { |id| find(id, :skip_exists => true) }.compact
|
57
68
|
end
|
58
69
|
|
59
70
|
# Returns an array of all worker objects currently processing
|
@@ -62,17 +73,15 @@ module Resque
|
|
62
73
|
names = all
|
63
74
|
return [] unless names.any?
|
64
75
|
|
65
|
-
names.map! { |name| "worker:#{name}" }
|
66
|
-
|
67
76
|
reportedly_working = {}
|
68
77
|
|
69
78
|
begin
|
70
|
-
reportedly_working =
|
79
|
+
reportedly_working = data_store.workers_map(names).reject do |key, value|
|
71
80
|
value.nil? || value.empty?
|
72
81
|
end
|
73
82
|
rescue Redis::Distributed::CannotDistribute
|
74
83
|
names.each do |name|
|
75
|
-
value =
|
84
|
+
value = data_store.get_worker_payload(name)
|
76
85
|
reportedly_working[name] = value unless value.nil? || value.empty?
|
77
86
|
end
|
78
87
|
end
|
@@ -92,6 +101,7 @@ module Resque
|
|
92
101
|
host, pid, queues_raw = worker_id.split(':')
|
93
102
|
queues = queues_raw.split(',')
|
94
103
|
worker = new(*queues)
|
104
|
+
worker.hostname = host
|
95
105
|
worker.to_s = worker_id
|
96
106
|
worker.pid = pid.to_i
|
97
107
|
worker
|
@@ -108,7 +118,7 @@ module Resque
|
|
108
118
|
# Given a string worker id, return a boolean indicating whether the
|
109
119
|
# worker exists
|
110
120
|
def self.exists?(worker_id)
|
111
|
-
|
121
|
+
data_store.worker_exists?(worker_id)
|
112
122
|
end
|
113
123
|
|
114
124
|
# Workers should be initialized with an array of string queue
|
@@ -122,31 +132,41 @@ module Resque
|
|
122
132
|
# If passed a single "*", this Worker will operate on all queues
|
123
133
|
# in alphabetical order. Queues can be dynamically added or
|
124
134
|
# removed without needing to restart workers using this method.
|
135
|
+
#
|
136
|
+
# Workers should have `#prepare` called after they are initialized
|
137
|
+
# if you are running work on the worker.
|
125
138
|
def initialize(*queues)
|
126
139
|
@shutdown = nil
|
127
140
|
@paused = nil
|
128
141
|
@before_first_fork_hook_ran = false
|
129
142
|
|
130
|
-
|
131
|
-
self.
|
143
|
+
verbose_value = ENV['LOGGING'] || ENV['VERBOSE']
|
144
|
+
self.verbose = verbose_value if verbose_value
|
145
|
+
self.very_verbose = ENV['VVERBOSE'] if ENV['VVERBOSE']
|
132
146
|
self.term_timeout = ENV['RESQUE_TERM_TIMEOUT'] || 4.0
|
133
147
|
self.term_child = ENV['TERM_CHILD']
|
134
148
|
self.graceful_term = ENV['GRACEFUL_TERM']
|
135
149
|
self.run_at_exit_hooks = ENV['RUN_AT_EXIT_HOOKS']
|
136
150
|
|
151
|
+
self.queues = queues
|
152
|
+
end
|
153
|
+
|
154
|
+
# Daemonizes the worker if ENV['BACKGROUND'] is set and writes
|
155
|
+
# the process id to ENV['PIDFILE'] if set. Should only be called
|
156
|
+
# once per worker.
|
157
|
+
def prepare
|
137
158
|
if ENV['BACKGROUND']
|
138
159
|
unless Process.respond_to?('daemon')
|
139
160
|
abort "env var BACKGROUND is set, which requires ruby >= 1.9"
|
140
161
|
end
|
141
162
|
Process.daemon(true)
|
142
|
-
self.reconnect
|
143
163
|
end
|
144
164
|
|
145
165
|
if ENV['PIDFILE']
|
146
166
|
File.open(ENV['PIDFILE'], 'w') { |f| f << pid }
|
147
167
|
end
|
148
168
|
|
149
|
-
self.
|
169
|
+
self.reconnect if ENV['BACKGROUND']
|
150
170
|
end
|
151
171
|
|
152
172
|
def queues=(queues)
|
@@ -200,46 +220,12 @@ module Resque
|
|
200
220
|
# has completed processing. Useful for testing.
|
201
221
|
def work(interval = 5.0, &block)
|
202
222
|
interval = Float(interval)
|
203
|
-
$0 = "resque: Starting"
|
204
223
|
startup
|
205
224
|
|
206
225
|
loop do
|
207
226
|
break if shutdown?
|
208
227
|
|
209
|
-
|
210
|
-
log_with_severity :info, "got: #{job.inspect}"
|
211
|
-
job.worker = self
|
212
|
-
working_on job
|
213
|
-
|
214
|
-
procline "Processing #{job.queue} since #{Time.now.to_i} [#{job.payload_class_name}]"
|
215
|
-
if @child = fork(job)
|
216
|
-
srand # Reseeding
|
217
|
-
procline "Forked #{@child} at #{Time.now.to_i}"
|
218
|
-
begin
|
219
|
-
Process.waitpid(@child)
|
220
|
-
rescue SystemCallError
|
221
|
-
nil
|
222
|
-
end
|
223
|
-
job.fail(DirtyExit.new("Child process received unhandled signal #{$?.stopsig}")) if $?.signaled?
|
224
|
-
else
|
225
|
-
unregister_signal_handlers if will_fork? && term_child
|
226
|
-
begin
|
227
|
-
|
228
|
-
reconnect if will_fork?
|
229
|
-
perform(job, &block)
|
230
|
-
|
231
|
-
rescue Exception => exception
|
232
|
-
report_failed_job(job,exception)
|
233
|
-
end
|
234
|
-
|
235
|
-
if will_fork?
|
236
|
-
run_at_exit_hooks ? exit : exit!
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
done_working
|
241
|
-
@child = nil
|
242
|
-
else
|
228
|
+
unless work_one_job(&block)
|
243
229
|
break if interval.zero?
|
244
230
|
log_with_severity :debug, "Sleeping for #{interval} seconds"
|
245
231
|
procline paused? ? "Paused" : "Waiting for #{queues.join(',')}"
|
@@ -249,11 +235,29 @@ module Resque
|
|
249
235
|
|
250
236
|
unregister_worker
|
251
237
|
rescue Exception => exception
|
252
|
-
|
253
|
-
|
238
|
+
return if exception.class == SystemExit && !@child && run_at_exit_hooks
|
239
|
+
log_with_severity :error, "Failed to start worker : #{exception.inspect}"
|
240
|
+
unregister_worker(exception)
|
241
|
+
end
|
242
|
+
|
243
|
+
def work_one_job(job = nil, &block)
|
244
|
+
return false if paused?
|
245
|
+
return false unless job ||= reserve
|
246
|
+
|
247
|
+
working_on job
|
248
|
+
procline "Processing #{job.queue} since #{Time.now.to_i} [#{job.payload_class_name}]"
|
254
249
|
|
255
|
-
|
250
|
+
log_with_severity :info, "got: #{job.inspect}"
|
251
|
+
job.worker = self
|
252
|
+
|
253
|
+
if fork_per_job?
|
254
|
+
perform_with_fork(job, &block)
|
255
|
+
else
|
256
|
+
perform(job, &block)
|
256
257
|
end
|
258
|
+
|
259
|
+
done_working
|
260
|
+
true
|
257
261
|
end
|
258
262
|
|
259
263
|
# DEPRECATED. Processes a single job. If none is given, it will
|
@@ -283,10 +287,14 @@ module Resque
|
|
283
287
|
end
|
284
288
|
end
|
285
289
|
|
290
|
+
|
286
291
|
# Processes a given job in the child.
|
287
292
|
def perform(job)
|
288
293
|
begin
|
289
|
-
|
294
|
+
if fork_per_job?
|
295
|
+
reconnect
|
296
|
+
run_hook :after_fork, job
|
297
|
+
end
|
290
298
|
job.perform
|
291
299
|
rescue Object => e
|
292
300
|
report_failed_job(job,e)
|
@@ -320,7 +328,7 @@ module Resque
|
|
320
328
|
def reconnect
|
321
329
|
tries = 0
|
322
330
|
begin
|
323
|
-
|
331
|
+
data_store.reconnect
|
324
332
|
rescue Redis::BaseConnectionError
|
325
333
|
if (tries += 1) <= 3
|
326
334
|
log_with_severity :error, "Error reconnecting to Redis; retrying"
|
@@ -333,30 +341,10 @@ module Resque
|
|
333
341
|
end
|
334
342
|
end
|
335
343
|
|
336
|
-
# Not every platform supports fork. Here we do our magic to
|
337
|
-
# determine if yours does.
|
338
|
-
def fork(job)
|
339
|
-
return unless will_fork?
|
340
|
-
|
341
|
-
# Only run before_fork hooks if we're actually going to fork
|
342
|
-
# (after checking will_fork?)
|
343
|
-
run_hook :before_fork, job
|
344
|
-
|
345
|
-
begin
|
346
|
-
# IronRuby doesn't support `Kernel.fork` yet
|
347
|
-
if Kernel.respond_to?(:fork)
|
348
|
-
Kernel.fork
|
349
|
-
else
|
350
|
-
raise NotImplementedError
|
351
|
-
end
|
352
|
-
rescue NotImplementedError
|
353
|
-
@cant_fork = true
|
354
|
-
nil
|
355
|
-
end
|
356
|
-
end
|
357
|
-
|
358
344
|
# Runs all the methods needed when a worker begins its lifecycle.
|
359
345
|
def startup
|
346
|
+
$0 = "resque: Starting"
|
347
|
+
|
360
348
|
enable_gc_optimizations
|
361
349
|
register_signal_handlers
|
362
350
|
start_heartbeat
|
@@ -468,12 +456,19 @@ module Resque
|
|
468
456
|
end
|
469
457
|
|
470
458
|
def heartbeat
|
471
|
-
heartbeat
|
472
|
-
|
459
|
+
data_store.heartbeat(self)
|
460
|
+
end
|
461
|
+
|
462
|
+
def remove_heartbeat
|
463
|
+
data_store.remove_heartbeat(self)
|
464
|
+
end
|
465
|
+
|
466
|
+
def heartbeat!(time = data_store.server_time)
|
467
|
+
data_store.heartbeat!(self, time)
|
473
468
|
end
|
474
469
|
|
475
470
|
def self.all_heartbeats
|
476
|
-
|
471
|
+
data_store.all_heartbeats
|
477
472
|
end
|
478
473
|
|
479
474
|
# Returns a list of workers that have sent a heartbeat in the past, but which
|
@@ -481,13 +476,14 @@ module Resque
|
|
481
476
|
def self.all_workers_with_expired_heartbeats
|
482
477
|
workers = Worker.all
|
483
478
|
heartbeats = Worker.all_heartbeats
|
479
|
+
now = data_store.server_time
|
484
480
|
|
485
481
|
workers.select do |worker|
|
486
482
|
id = worker.to_s
|
487
483
|
heartbeat = heartbeats[id]
|
488
484
|
|
489
485
|
if heartbeat
|
490
|
-
seconds_since_heartbeat = (
|
486
|
+
seconds_since_heartbeat = (now - Time.parse(heartbeat)).to_i
|
491
487
|
seconds_since_heartbeat > Resque.prune_interval
|
492
488
|
else
|
493
489
|
false
|
@@ -495,18 +491,20 @@ module Resque
|
|
495
491
|
end
|
496
492
|
end
|
497
493
|
|
498
|
-
def heartbeat!(time = Time.now)
|
499
|
-
redis.hset(WORKER_HEARTBEAT_KEY, to_s, time.iso8601)
|
500
|
-
end
|
501
|
-
|
502
494
|
def start_heartbeat
|
503
|
-
|
504
|
-
|
495
|
+
remove_heartbeat
|
496
|
+
|
497
|
+
@heartbeat_thread_signal = Resque::ThreadSignal.new
|
498
|
+
|
499
|
+
@heartbeat_thread = Thread.new do
|
505
500
|
loop do
|
506
|
-
sleep(Resque.heartbeat_interval)
|
507
501
|
heartbeat!
|
502
|
+
signaled = @heartbeat_thread_signal.wait_for_signal(Resque.heartbeat_interval)
|
503
|
+
break if signaled
|
508
504
|
end
|
509
505
|
end
|
506
|
+
|
507
|
+
@@all_heartbeat_threads << @heartbeat_thread
|
510
508
|
end
|
511
509
|
|
512
510
|
# Kills the forked child immediately with minimal remorse. The job it
|
@@ -604,10 +602,7 @@ module Resque
|
|
604
602
|
# Registers ourself as a worker. Useful when entering the worker
|
605
603
|
# lifecycle on startup.
|
606
604
|
def register_worker
|
607
|
-
|
608
|
-
redis.sadd(:workers, self)
|
609
|
-
started!
|
610
|
-
end
|
605
|
+
data_store.register_worker(self)
|
611
606
|
end
|
612
607
|
|
613
608
|
# Runs a named hook, passing along any arguments.
|
@@ -625,7 +620,10 @@ module Resque
|
|
625
620
|
end
|
626
621
|
|
627
622
|
def kill_background_threads
|
628
|
-
|
623
|
+
if @heartbeat_thread
|
624
|
+
@heartbeat_thread_signal.signal
|
625
|
+
@heartbeat_thread.join
|
626
|
+
end
|
629
627
|
end
|
630
628
|
|
631
629
|
# Unregisters ourself as a worker. Useful when shutting down.
|
@@ -646,20 +644,15 @@ module Resque
|
|
646
644
|
|
647
645
|
kill_background_threads
|
648
646
|
|
649
|
-
|
650
|
-
redis.srem(:workers, self)
|
651
|
-
redis.del("worker:#{self}")
|
652
|
-
redis.del("worker:#{self}:started")
|
653
|
-
redis.hdel(WORKER_HEARTBEAT_KEY, self.to_s)
|
654
|
-
|
647
|
+
data_store.unregister_worker(self) do
|
655
648
|
Stat.clear("processed:#{self}")
|
656
649
|
Stat.clear("failed:#{self}")
|
657
650
|
end
|
658
651
|
rescue Exception => exception_while_unregistering
|
659
652
|
message = exception_while_unregistering.message
|
660
653
|
if exception
|
661
|
-
message
|
662
|
-
|
654
|
+
message += "\nOriginal Exception (#{exception.class}): #{exception.message}"
|
655
|
+
message += "\n #{exception.backtrace.join(" \n")}" if exception.backtrace
|
663
656
|
end
|
664
657
|
fail(exception_while_unregistering.class,
|
665
658
|
message,
|
@@ -673,15 +666,14 @@ module Resque
|
|
673
666
|
:queue => job.queue,
|
674
667
|
:run_at => Time.now.utc.iso8601,
|
675
668
|
:payload => job.payload
|
676
|
-
|
669
|
+
data_store.set_worker_payload(self,data)
|
677
670
|
end
|
678
671
|
|
679
672
|
# Called when we are done working - clears our `working_on` state
|
680
673
|
# and tells Redis we processed a job.
|
681
674
|
def done_working
|
682
|
-
|
675
|
+
data_store.worker_done_working(self) do
|
683
676
|
processed!
|
684
|
-
redis.del("worker:#{self}")
|
685
677
|
end
|
686
678
|
end
|
687
679
|
|
@@ -709,18 +701,18 @@ module Resque
|
|
709
701
|
|
710
702
|
# What time did this worker start? Returns an instance of `Time`
|
711
703
|
def started
|
712
|
-
|
704
|
+
data_store.worker_start_time(self)
|
713
705
|
end
|
714
706
|
|
715
707
|
# Tell Redis we've started
|
716
708
|
def started!
|
717
|
-
|
709
|
+
data_store.worker_started(self)
|
718
710
|
end
|
719
711
|
|
720
712
|
# Returns a hash explaining the Job we're currently processing, if any.
|
721
713
|
def job(reload = true)
|
722
714
|
@job = nil if reload
|
723
|
-
@job ||= decode(
|
715
|
+
@job ||= decode(data_store.get_worker_payload(self)) || {}
|
724
716
|
end
|
725
717
|
attr_writer :job
|
726
718
|
alias_method :processing, :job
|
@@ -735,18 +727,15 @@ module Resque
|
|
735
727
|
state == :idle
|
736
728
|
end
|
737
729
|
|
738
|
-
def will_fork?
|
739
|
-
!@cant_fork && fork_per_job?
|
740
|
-
end
|
741
|
-
|
742
730
|
def fork_per_job?
|
743
|
-
|
731
|
+
return @fork_per_job if defined?(@fork_per_job)
|
732
|
+
@fork_per_job = ENV["FORK_PER_JOB"] != 'false' && Kernel.respond_to?(:fork)
|
744
733
|
end
|
745
734
|
|
746
735
|
# Returns a symbol representing the current worker state,
|
747
736
|
# which can be either :working or :idle
|
748
737
|
def state
|
749
|
-
|
738
|
+
data_store.get_worker_payload(self) ? :working : :idle
|
750
739
|
end
|
751
740
|
|
752
741
|
# Is this worker the same as another worker?
|
@@ -765,9 +754,9 @@ module Resque
|
|
765
754
|
end
|
766
755
|
alias_method :id, :to_s
|
767
756
|
|
768
|
-
# chomp'd hostname of this machine
|
757
|
+
# chomp'd hostname of this worker's machine
|
769
758
|
def hostname
|
770
|
-
Socket.gethostname
|
759
|
+
@hostname ||= Socket.gethostname
|
771
760
|
end
|
772
761
|
|
773
762
|
# Returns Integer PID of running worker
|
@@ -797,7 +786,7 @@ module Resque
|
|
797
786
|
# Find Resque worker pids on Linux and OS X.
|
798
787
|
#
|
799
788
|
def linux_worker_pids
|
800
|
-
`ps -A -o pid,command | grep -E "[r]esque:work|[r]esque-[0-9]" | grep -v "resque-web"`.split("\n").map do |line|
|
789
|
+
`ps -A -o pid,command | grep -E "[r]esque:work|[r]esque:\sStarting|[r]esque-[0-9]" | grep -v "resque-web"`.split("\n").map do |line|
|
801
790
|
line.split(' ')[0]
|
802
791
|
end
|
803
792
|
end
|
@@ -868,6 +857,34 @@ module Resque
|
|
868
857
|
|
869
858
|
private
|
870
859
|
|
860
|
+
def perform_with_fork(job, &block)
|
861
|
+
run_hook :before_fork, job
|
862
|
+
|
863
|
+
begin
|
864
|
+
@child = fork do
|
865
|
+
unregister_signal_handlers if term_child
|
866
|
+
perform(job, &block)
|
867
|
+
exit! unless run_at_exit_hooks
|
868
|
+
end
|
869
|
+
rescue NotImplementedError
|
870
|
+
@fork_per_job = false
|
871
|
+
perform(job, &block)
|
872
|
+
return
|
873
|
+
end
|
874
|
+
|
875
|
+
srand # Reseeding
|
876
|
+
procline "Forked #{@child} at #{Time.now.to_i}"
|
877
|
+
|
878
|
+
begin
|
879
|
+
Process.waitpid(@child)
|
880
|
+
rescue SystemCallError
|
881
|
+
nil
|
882
|
+
end
|
883
|
+
|
884
|
+
job.fail(DirtyExit.new("Child process received unhandled signal #{$?.stopsig}", $?)) if $?.signaled?
|
885
|
+
@child = nil
|
886
|
+
end
|
887
|
+
|
871
888
|
def log_with_severity(severity, message)
|
872
889
|
Logging.log(severity, message)
|
873
890
|
end
|