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.

@@ -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
@@ -1,3 +1,3 @@
1
1
  module Resque
2
- Version = VERSION = '1.26.0'
2
+ Version = VERSION = '1.27.0'
3
3
  end
@@ -14,16 +14,25 @@ module Resque
14
14
  extend Resque::Helpers
15
15
  include Resque::Logging
16
16
 
17
- WORKER_HEARTBEAT_KEY = "workers:heartbeat"
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
- Array(redis.smembers(:workers)).map { |id| find(id, :skip_exists => true) }.compact
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 = redis.mapped_mget(*names).reject do |key, value|
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 = redis.get name
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
- redis.sismember(:workers, worker_id)
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
- self.verbose = ENV['LOGGING'] || ENV['VERBOSE']
131
- self.very_verbose = ENV['VVERBOSE']
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.queues = queues
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
- if not paused? and job = reserve
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
- unless exception.class == SystemExit && !@child && run_at_exit_hooks
253
- log_with_severity :error, "Failed to start worker : #{exception.inspect}"
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
- unregister_worker(exception)
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
- run_hook :after_fork, job if will_fork?
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
- redis.client.reconnect
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 = redis.hget(WORKER_HEARTBEAT_KEY, to_s)
472
- heartbeat && Time.parse(heartbeat)
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
- redis.hgetall(WORKER_HEARTBEAT_KEY)
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 = (Time.now - Time.parse(heartbeat)).to_i
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
- heartbeat!
504
- @heart = Thread.new do
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
- redis.pipelined do
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
- @heart.kill if @heart
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
- redis.pipelined do
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 = message + "\nOriginal Exception (#{exception.class}): #{exception.message}\n" +
662
- " #{exception.backtrace.join(" \n")}"
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
- redis.set("worker:#{self}", data)
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
- redis.pipelined do
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
- redis.get "worker:#{self}:started"
704
+ data_store.worker_start_time(self)
713
705
  end
714
706
 
715
707
  # Tell Redis we've started
716
708
  def started!
717
- redis.set("worker:#{self}:started", Time.now.to_s)
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(redis.get("worker:#{self}")) || {}
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
- ENV["FORK_PER_JOB"] != 'false'
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
- redis.exists("worker:#{self}") ? :working : :idle
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