resque 1.27.3 → 2.2.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.

Files changed (39) hide show
  1. checksums.yaml +5 -5
  2. data/HISTORY.md +74 -1
  3. data/README.markdown +421 -486
  4. data/bin/resque-web +1 -1
  5. data/lib/resque/data_store.rb +23 -31
  6. data/lib/resque/errors.rb +7 -1
  7. data/lib/resque/failure/airbrake.rb +19 -7
  8. data/lib/resque/failure/multiple.rb +6 -2
  9. data/lib/resque/failure/redis.rb +1 -1
  10. data/lib/resque/failure/redis_multi_queue.rb +3 -3
  11. data/lib/resque/failure.rb +7 -0
  12. data/lib/resque/job.rb +2 -2
  13. data/lib/resque/logging.rb +1 -1
  14. data/lib/resque/railtie.rb +10 -0
  15. data/lib/resque/server/helpers.rb +3 -1
  16. data/lib/resque/server/public/jquery-3.6.0.min.js +2 -0
  17. data/lib/resque/server/public/main.js +3 -0
  18. data/lib/resque/server/public/ranger.js +7 -4
  19. data/lib/resque/server/public/style.css +3 -3
  20. data/lib/resque/server/test_helper.rb +1 -1
  21. data/lib/resque/server/views/failed.erb +8 -2
  22. data/lib/resque/server/views/failed_job.erb +2 -2
  23. data/lib/resque/server/views/layout.erb +4 -3
  24. data/lib/resque/server/views/next_more.erb +14 -14
  25. data/lib/resque/server/views/queues.erb +6 -6
  26. data/lib/resque/server/views/stats.erb +1 -1
  27. data/lib/resque/server/views/working.erb +6 -6
  28. data/lib/resque/server.rb +12 -9
  29. data/lib/resque/stat.rb +12 -5
  30. data/lib/resque/tasks.rb +1 -9
  31. data/lib/resque/thread_signal.rb +13 -34
  32. data/lib/resque/vendor/utf8_util.rb +2 -8
  33. data/lib/resque/version.rb +1 -1
  34. data/lib/resque/worker.rb +55 -36
  35. data/lib/resque.rb +92 -18
  36. metadata +16 -15
  37. data/lib/resque/server/public/jquery-1.12.4.min.js +0 -5
  38. data/lib/resque/vendor/utf8_util/utf8_util_18.rb +0 -91
  39. data/lib/resque/vendor/utf8_util/utf8_util_19.rb +0 -6
@@ -6,10 +6,10 @@
6
6
  <% else %>
7
7
  <dt>Worker</dt>
8
8
  <dd>
9
- <a href="<%= u(:workers, job['worker']) %>"><%= job['worker'].split(':')[0...2].join(':') %></a> on <b class='queue-tag'><%= job['queue'] %></b > at <b><span class="time"><%= Time.parse(job['failed_at']).strftime(failed_date_format) %></span></b>
9
+ <a href="<%= u(:workers, job['worker']) %>"><%= job['worker'].split(':')[0...2].join(':') %></a> on <b class='queue-tag'><%= job['queue'] %></b > at <b><span class="time"><%= DateTime.parse(job['failed_at']).strftime(failed_date_format) %></span></b>
10
10
  <% if job['retried_at'] %>
11
11
  <div class='retried'>
12
- Retried <b><span class="time"><%= Time.parse(job['retried_at']).strftime(failed_date_format) %></span></b>
12
+ Retried <b><span class="time"><%= DateTime.parse(job['retried_at']).strftime(failed_date_format) %></span></b>
13
13
  <a href="<%= u "#{queue}/remove/#{id}" %>" class="remove" rel="remove">Remove</a>
14
14
  </div>
15
15
  <% else %>
@@ -5,9 +5,10 @@
5
5
  <title>Resque</title>
6
6
  <link href="<%=u 'reset.css' %>" media="screen" rel="stylesheet" type="text/css">
7
7
  <link href="<%=u 'style.css' %>" media="screen" rel="stylesheet" type="text/css">
8
- <script src="<%=u 'jquery-1.12.4.min.js' %>" type="text/javascript"></script>
8
+ <script src="<%=u 'jquery-3.6.0.min.js' %>" type="text/javascript"></script>
9
9
  <script src="<%=u 'jquery.relatize_date.js' %>" type="text/javascript"></script>
10
10
  <script src="<%=u 'ranger.js' %>" type="text/javascript"></script>
11
+ <script src="<%=u 'main.js' %>" type="text/javascript"></script>
11
12
  </head>
12
13
  <body>
13
14
  <div class="header">
@@ -23,7 +24,7 @@
23
24
  <% end %>
24
25
  </div>
25
26
 
26
- <% if @subtabs %>
27
+ <% if defined?(@subtabs) && @subtabs %>
27
28
  <ul class='subnav'>
28
29
  <% for subtab in @subtabs %>
29
30
  <li <%= class_if_current "#{current_section}/#{subtab}" %>><a href="<%= current_section %>/<%= subtab %>"><span><%= subtab %></span></a></li>
@@ -36,7 +37,7 @@
36
37
  </div>
37
38
 
38
39
  <div id="footer">
39
- <p>Powered by <a href="http://github.com/resque/resque">Resque</a> v<%=Resque::Version%></p>
40
+ <p>Powered by <a href="http://github.com/resque/resque">Resque</a> v<%=Resque::VERSION%></p>
40
41
  <p>Connected to Redis namespace <%= Resque.redis.namespace %> on <%=Resque.redis_id%></p>
41
42
  </div>
42
43
 
@@ -2,21 +2,21 @@
2
2
  <% # without a default value %>
3
3
  <% per_page ||= 20 %>
4
4
  <%if start - per_page >= 0 || start + per_page <= size%>
5
- <p class='pagination'>
6
- <% if start + per_page <= size %>
7
- <a href="<%= current_page %>?start=<%= start + per_page %>" class='more'>&laquo; Next</a>
8
- <% end %>
5
+ <div class='pagination'>
6
+ <% if start - per_page >= 0 %>
7
+ <a href="<%= current_page %>?start=<%= start - per_page %>" class='less'>&laquo; Previous</a>
8
+ <% end %>
9
9
 
10
- <% (size / per_page.to_f - 1).ceil.downto(0).each do |page_num| %>
11
- <% if start == page_num * per_page %>
12
- <%= page_num %>
13
- <% else %>
14
- <a href="<%= current_page %>?start=<%= page_num * per_page %>"> <%= page_num %></a>
10
+ <% (size / per_page.to_f - 1).ceil.times do |page_num| %>
11
+ <% if start == page_num * per_page %>
12
+ <span><%= page_num + 1 %></span>
13
+ <% else %>
14
+ <a href="<%= current_page %>?start=<%= page_num * per_page %>"> <%= page_num + 1 %></a>
15
+ <% end %>
15
16
  <% end %>
16
- <% end %>
17
17
 
18
- <% if start - per_page >= 0 %>
19
- <a href="<%= current_page %>?start=<%= start - per_page %>" class='less'>Previous &raquo;</a>
20
- <% end %>
21
- </p>
18
+ <% if start + per_page <= size %>
19
+ <a href="<%= current_page %>?start=<%= start + per_page %>" class='more'>Next &raquo;</a>
20
+ <% end %>
21
+ </div>
22
22
  <%end%>
@@ -1,18 +1,18 @@
1
1
  <% @subtabs = resque.queues unless partial? || params[:id].nil? %>
2
2
 
3
- <% if queue = params[:id] %>
3
+ <% if current_queue = params[:id] %>
4
4
 
5
- <h1>Pending jobs on <span class='hl'><%= queue %></span></h1>
6
- <form method="POST" action="<%=u "/queues/#{queue}/remove" %>" class='remove-queue'>
7
- <input type='submit' name='' value='Remove Queue' onclick='return confirm("Are you absolutely sure? This cannot be undone.");' />
5
+ <h1>Pending jobs on <span class='hl'><%= h current_queue %></span></h1>
6
+ <form method="POST" action="<%=u "/queues/#{current_queue}/remove" %>" class='remove-queue'>
7
+ <input type='submit' name='' value='Remove Queue' class="confirmSubmission" />
8
8
  </form>
9
- <p class='sub'><%= page_entries_info start = params[:start].to_i, start + 19, size = resque.size(queue), 'job' %></p>
9
+ <p class='sub'><%= page_entries_info start = params[:start].to_i, start + 19, size = resque.size(current_queue), 'job' %></p>
10
10
  <table class='jobs'>
11
11
  <tr>
12
12
  <th>Class</th>
13
13
  <th>Args</th>
14
14
  </tr>
15
- <% for job in (jobs = resque.peek(queue, start, 20)) %>
15
+ <% for job in (jobs = resque.peek(current_queue, start, 20)) %>
16
16
  <tr>
17
17
  <td class='class'><%= partial :job_class, :job => job %></td>
18
18
  <td class='args'><%=h job['args'].inspect %></td>
@@ -24,7 +24,7 @@
24
24
 
25
25
  <h1><%= resque.redis_id %></h1>
26
26
  <table class='stats'>
27
- <% for key, value in resque.redis.info.to_a.sort_by { |i| i[0].to_s } %>
27
+ <% for key, value in resque.redis.redis.info.to_a.sort_by { |i| i[0].to_s } %>
28
28
  <tr>
29
29
  <th>
30
30
  <%= key %>
@@ -1,5 +1,5 @@
1
- <% if params[:id] && (worker = Resque::Worker.find(params[:id])) && (data = worker.job) %>
2
- <h1><%= worker %>'s job</h1>
1
+ <% if params[:id] && (current_worker = Resque::Worker.find(params[:id])) && (data = current_worker.job) %>
2
+ <h1><%= current_worker %>'s job</h1>
3
3
 
4
4
  <table>
5
5
  <tr>
@@ -12,8 +12,8 @@
12
12
  </tr>
13
13
  <tr>
14
14
  <td><img src="<%=u 'working.png' %>" alt="working" title="working"></td>
15
- <% host, pid, _ = worker.to_s.split(':') %>
16
- <td><a href="<%=u "/workers/#{worker}" %>"><%= host %>:<%= pid %></a></td>
15
+ <% host, pid, _ = current_worker.to_s.split(':') %>
16
+ <td><a href="<%=u "/workers/#{current_worker}" %>"><%= host %>:<%= pid %></a></td>
17
17
  <% queue = data['queue'] %>
18
18
  <td><a class="queue" href="<%=u "/queues/#{queue}" %>"><%= queue %></a></td>
19
19
  <td><span class="time"><%= data['run_at'] %></span></td>
@@ -49,10 +49,10 @@
49
49
  </tr>
50
50
  <% end %>
51
51
 
52
- <% worker_jobs.sort_by {|w, j| j['run_at'] ? j['run_at'].to_s() : '' }.each do |worker, job| %>
52
+ <% worker_jobs.sort_by { |_w, j| j['run_at'] ? j['run_at'].to_s() : '' }.each do |worker, job| %>
53
53
  <tr>
54
54
  <td class='icon'><img src="<%=u state = worker.state %>.png" alt="<%= state %>" title="<%= state %>"></td>
55
- <% host, pid, queues = worker.to_s.split(':') %>
55
+ <% host, pid, _queues = worker.to_s.split(':') %>
56
56
  <td class='where'><a href="<%=u "/workers/#{worker}" %>"><%= host %>:<%= pid %></a></td>
57
57
  <td class='queues queue'>
58
58
  <a class="queue-tag" href="<%=u "/queues/#{job['queue']}" %>"><%= job['queue'] %></a>
data/lib/resque/server.rb CHANGED
@@ -5,7 +5,7 @@ require 'resque/version'
5
5
  require 'time'
6
6
  require 'yaml'
7
7
 
8
- if defined? Encoding
8
+ if defined?(Encoding) && Encoding.default_external != Encoding::UTF_8
9
9
  Encoding.default_external = Encoding::UTF_8
10
10
  end
11
11
 
@@ -42,10 +42,6 @@ module Resque
42
42
  end
43
43
  alias_method :u, :url_path
44
44
 
45
- def redirect_url_path(*path_parts)
46
- [ path_prefix, path_parts ].join("/").squeeze('/')
47
- end
48
-
49
45
  def path_prefix
50
46
  request.env['SCRIPT_NAME']
51
47
  end
@@ -102,6 +98,8 @@ module Resque
102
98
  Array(args).map do |a|
103
99
  a.to_yaml
104
100
  end.join("\n")
101
+ rescue
102
+ args.to_s
105
103
  end
106
104
 
107
105
  def worker_hosts
@@ -131,7 +129,7 @@ module Resque
131
129
  end
132
130
 
133
131
  def poll
134
- if @polling
132
+ if defined?(@polling) && @polling
135
133
  text = "Last Updated: #{Time.now.strftime("%H:%M:%S")}"
136
134
  else
137
135
  text = "<a href='#{u(request.path_info)}.poll' rel='poll'>Live Poll</a>"
@@ -158,7 +156,7 @@ module Resque
158
156
 
159
157
  # to make things easier on ourselves
160
158
  get "/?" do
161
- redirect redirect_url_path(:overview)
159
+ redirect url_path(:overview)
162
160
  end
163
161
 
164
162
  %w( overview workers ).each do |page|
@@ -207,6 +205,11 @@ module Resque
207
205
  redirect u('failed')
208
206
  end
209
207
 
208
+ post "/failed/clear_retried" do
209
+ Resque::Failure.clear_retried
210
+ redirect u('failed')
211
+ end
212
+
210
213
  post "/failed/:queue/clear" do
211
214
  Resque::Failure.clear params[:queue]
212
215
  redirect u('failed')
@@ -219,7 +222,7 @@ module Resque
219
222
 
220
223
  post "/failed/:queue/requeue/all" do
221
224
  Resque::Failure.requeue_queue Resque::Failure.job_queue_name(params[:queue])
222
- redirect redirect_url_path("/failed/#{params[:queue]}")
225
+ redirect url_path("/failed/#{params[:queue]}")
223
226
  end
224
227
 
225
228
  get "/failed/requeue/:index/?" do
@@ -251,7 +254,7 @@ module Resque
251
254
  end
252
255
 
253
256
  get "/stats/?" do
254
- redirect redirect_url_path("/stats/resque")
257
+ redirect url_path("/stats/resque")
255
258
  end
256
259
 
257
260
  get "/stats/:id/?" do
data/lib/resque/stat.rb CHANGED
@@ -7,12 +7,19 @@ module Resque
7
7
  # Kill a stat: Stat.clear(name)
8
8
  module Stat
9
9
  extend self
10
-
11
- # Direct access to the Redis instance.
10
+
12
11
  def redis
13
- Resque.redis
12
+ warn '[Resque] [Deprecation] Resque::Stat #redis method is deprecated (please use #data_strore)'
13
+ data_store
14
+ end
15
+
16
+ def data_store
17
+ @data_store ||= Resque.redis
18
+ end
19
+
20
+ def data_store=(data_store)
21
+ @data_store = data_store
14
22
  end
15
- alias :data_store :redis
16
23
 
17
24
  # Returns the int value of a stat, given a string stat name.
18
25
  def get(stat)
@@ -42,7 +49,7 @@ module Resque
42
49
  # Can optionally accept a second int parameter. The stat is then
43
50
  # decremented by that amount.
44
51
  def decr(stat, by = 1)
45
- data_store.decremet_stat(stat,by)
52
+ data_store.decrement_stat(stat,by)
46
53
  end
47
54
 
48
55
  # Decrements a stat by one.
data/lib/resque/tasks.rb CHANGED
@@ -40,17 +40,9 @@ namespace :resque do
40
40
  # Preload app files if this is Rails
41
41
  task :preload => :setup do
42
42
  if defined?(Rails)
43
- if Rails::VERSION::MAJOR > 3
43
+ if Rails.application.config.eager_load
44
44
  ActiveSupport.run_load_hooks(:before_eager_load, Rails.application)
45
45
  Rails.application.config.eager_load_namespaces.each(&:eager_load!)
46
-
47
- elsif Rails::VERSION::MAJOR == 3
48
- ActiveSupport.run_load_hooks(:before_eager_load, Rails.application)
49
- Rails.application.eager_load!
50
-
51
- elsif defined?(Rails::Initializer)
52
- $rails_rake_task = false
53
- Rails::Initializer.run :load_application_classes
54
46
  end
55
47
  end
56
48
  end
@@ -1,45 +1,24 @@
1
1
  class Resque::ThreadSignal
2
- if RUBY_VERSION <= "1.9"
3
- def initialize
4
- @signaled = false
5
- end
2
+ def initialize
3
+ @mutex = Mutex.new
4
+ @signaled = false
5
+ @received = ConditionVariable.new
6
+ end
6
7
 
7
- def signal
8
+ def signal
9
+ @mutex.synchronize do
8
10
  @signaled = true
11
+ @received.signal
9
12
  end
13
+ end
10
14
 
11
- def wait_for_signal(timeout)
12
- (10 * timeout).times do
13
- sleep(0.1)
14
- return true if @signaled
15
+ def wait_for_signal(timeout)
16
+ @mutex.synchronize do
17
+ unless @signaled
18
+ @received.wait(@mutex, timeout)
15
19
  end
16
20
 
17
21
  @signaled
18
22
  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
23
  end
45
24
  end
@@ -7,7 +7,8 @@ module UTF8Util
7
7
  #
8
8
  # Returns self as valid UTF-8.
9
9
  def self.clean!(str)
10
- raise NotImplementedError
10
+ return str if str.encoding.to_s == "UTF-8"
11
+ str.force_encoding("binary").encode("UTF-8", :invalid => :replace, :undef => :replace, :replace => REPLACEMENT_CHAR)
11
12
  end
12
13
 
13
14
  # Replace invalid UTF-8 character sequences with a replacement character
@@ -16,11 +17,4 @@ module UTF8Util
16
17
  def self.clean(str)
17
18
  clean!(str.dup)
18
19
  end
19
-
20
- end
21
-
22
- if RUBY_VERSION <= '1.9'
23
- require 'resque/vendor/utf8_util/utf8_util_18'
24
- else
25
- require 'resque/vendor/utf8_util/utf8_util_19'
26
20
  end
@@ -1,3 +1,3 @@
1
1
  module Resque
2
- Version = VERSION = '1.27.3'
2
+ VERSION = '2.2.0'
3
3
  end
data/lib/resque/worker.rb CHANGED
@@ -103,7 +103,7 @@ module Resque
103
103
  skip_exists = options[:skip_exists]
104
104
 
105
105
  if skip_exists || exists?(worker_id)
106
- host, pid, queues_raw = worker_id.split(':')
106
+ host, pid, queues_raw = worker_id.split(':', 3)
107
107
  queues = queues_raw.split(',')
108
108
  worker = new(*queues)
109
109
  worker.hostname = host
@@ -145,6 +145,11 @@ module Resque
145
145
  @paused = nil
146
146
  @before_first_fork_hook_ran = false
147
147
 
148
+ @heartbeat_thread = nil
149
+ @heartbeat_thread_signal = nil
150
+
151
+ @last_state = :idle
152
+
148
153
  verbose_value = ENV['LOGGING'] || ENV['VERBOSE']
149
154
  self.verbose = verbose_value if verbose_value
150
155
  self.very_verbose = ENV['VVERBOSE'] if ENV['VVERBOSE']
@@ -162,9 +167,6 @@ module Resque
162
167
  # once per worker.
163
168
  def prepare
164
169
  if ENV['BACKGROUND']
165
- unless Process.respond_to?('daemon')
166
- abort "env var BACKGROUND is set, which requires ruby >= 1.9"
167
- end
168
170
  Process.daemon(true)
169
171
  end
170
172
 
@@ -175,12 +177,12 @@ module Resque
175
177
  self.reconnect if ENV['BACKGROUND']
176
178
  end
177
179
 
180
+ WILDCARDS = ['*', '?', '{', '}', '[', ']'].freeze
181
+
178
182
  def queues=(queues)
179
183
  queues = queues.empty? ? (ENV["QUEUES"] || ENV['QUEUE']).to_s.split(',') : queues
180
184
  @queues = queues.map { |queue| queue.to_s.strip }
181
- unless ['*', '?', '{', '}', '[', ']'].any? {|char| @queues.join.include?(char) }
182
- @static_queues = @queues.flatten.uniq
183
- end
185
+ @has_dynamic_queues = WILDCARDS.any? {|char| @queues.join.include?(char) }
184
186
  validate_queues
185
187
  end
186
188
 
@@ -198,12 +200,16 @@ module Resque
198
200
  # A splat ("*") means you want every queue (in alpha order) - this
199
201
  # can be useful for dynamically adding new queues.
200
202
  def queues
201
- return @static_queues if @static_queues
202
- @queues.map { |queue| glob_match(queue) }.flatten.uniq
203
+ if @has_dynamic_queues
204
+ current_queues = Resque.queues
205
+ @queues.map { |queue| glob_match(current_queues, queue) }.flatten.uniq
206
+ else
207
+ @queues
208
+ end
203
209
  end
204
210
 
205
- def glob_match(pattern)
206
- Resque.queues.select do |queue|
211
+ def glob_match(list, pattern)
212
+ list.select do |queue|
207
213
  File.fnmatch?(pattern, queue)
208
214
  end.sort
209
215
  end
@@ -232,6 +238,7 @@ module Resque
232
238
  break if shutdown?
233
239
 
234
240
  unless work_one_job(&block)
241
+ state_change
235
242
  break if interval.zero?
236
243
  log_with_severity :debug, "Sleeping for #{interval} seconds"
237
244
  procline paused? ? "Paused" : "Waiting for #{queues.join(',')}"
@@ -240,10 +247,12 @@ module Resque
240
247
  end
241
248
 
242
249
  unregister_worker
250
+ run_hook :worker_exit
243
251
  rescue Exception => exception
244
252
  return if exception.class == SystemExit && !@child && run_at_exit_hooks
245
253
  log_with_severity :error, "Failed to start worker : #{exception.inspect}"
246
254
  unregister_worker(exception)
255
+ run_hook :worker_exit
247
256
  end
248
257
 
249
258
  def work_one_job(job = nil, &block)
@@ -482,20 +491,22 @@ module Resque
482
491
  # Returns a list of workers that have sent a heartbeat in the past, but which
483
492
  # already expired (does NOT include workers that have never sent a heartbeat at all).
484
493
  def self.all_workers_with_expired_heartbeats
485
- workers = Worker.all
494
+ # Use `Worker.all_heartbeats` instead of `Worker.all`
495
+ # to prune workers which haven't been registered but have set a heartbeat.
496
+ # https://github.com/resque/resque/pull/1751
486
497
  heartbeats = Worker.all_heartbeats
487
498
  now = data_store.server_time
488
499
 
489
- workers.select do |worker|
490
- id = worker.to_s
491
- heartbeat = heartbeats[id]
492
-
500
+ heartbeats.select do |id, heartbeat|
493
501
  if heartbeat
494
502
  seconds_since_heartbeat = (now - Time.parse(heartbeat)).to_i
495
503
  seconds_since_heartbeat > Resque.prune_interval
496
504
  else
497
505
  false
498
506
  end
507
+ end.each_key.map do |id|
508
+ # skip_exists must be true to include not registered workers
509
+ find(id, :skip_exists => true)
499
510
  end
500
511
  end
501
512
 
@@ -588,23 +599,27 @@ module Resque
588
599
  # By checking the current Redis state against the actual
589
600
  # environment, we can determine if Redis is old and clean it up a bit.
590
601
  def prune_dead_workers
591
- all_workers = Worker.all
602
+ return unless data_store.acquire_pruning_dead_worker_lock(self, Resque.heartbeat_interval)
592
603
 
593
- unless all_workers.empty?
594
- known_workers = worker_pids
595
- all_workers_with_expired_heartbeats = Worker.all_workers_with_expired_heartbeats
596
- end
604
+ all_workers = Worker.all
597
605
 
598
- all_workers.each do |worker|
606
+ known_workers = worker_pids
607
+ all_workers_with_expired_heartbeats = Worker.all_workers_with_expired_heartbeats
608
+ all_workers_with_expired_heartbeats.each do |worker|
599
609
  # If the worker hasn't sent a heartbeat, remove it from the registry.
600
610
  #
601
611
  # If the worker hasn't ever sent a heartbeat, we won't remove it since
602
612
  # the first heartbeat is sent before the worker is registred it means
603
613
  # that this is a worker that doesn't support heartbeats, e.g., another
604
614
  # client library or an older version of Resque. We won't touch these.
615
+ log_with_severity :info, "Pruning dead worker: #{worker}"
616
+
617
+ job_class = worker.job(false)['payload']['class'] rescue nil
618
+ worker.unregister_worker(PruneDeadWorkerDirtyExit.new(worker.to_s, job_class))
619
+ end
620
+
621
+ all_workers.each do |worker|
605
622
  if all_workers_with_expired_heartbeats.include?(worker)
606
- log_with_severity :info, "Pruning dead worker: #{worker}"
607
- worker.unregister_worker(PruneDeadWorkerDirtyExit.new(worker.to_s))
608
623
  next
609
624
  end
610
625
 
@@ -635,7 +650,8 @@ module Resque
635
650
 
636
651
  # Runs a named hook, passing along any arguments.
637
652
  def run_hook(name, *args)
638
- return unless hooks = Resque.send(name)
653
+ hooks = Resque.send(name)
654
+ return if hooks.empty?
639
655
  return if name == :before_first_fork && @before_first_fork_hook_ran
640
656
  msg = "Running #{name} hooks"
641
657
  msg << " with #{args.inspect}" if args.any?
@@ -695,6 +711,7 @@ module Resque
695
711
  :run_at => Time.now.utc.iso8601,
696
712
  :payload => job.payload
697
713
  data_store.set_worker_payload(self,data)
714
+ state_change
698
715
  end
699
716
 
700
717
  # Called when we are done working - clears our `working_on` state
@@ -705,6 +722,14 @@ module Resque
705
722
  end
706
723
  end
707
724
 
725
+ def state_change
726
+ current_state = state
727
+ if current_state != @last_state
728
+ run_hook :queue_empty if current_state == :idle
729
+ @last_state = current_state
730
+ end
731
+ end
732
+
708
733
  # How many jobs has this worker processed? Returns an int.
709
734
  def processed
710
735
  Stat["processed:#{self}"]
@@ -808,7 +833,7 @@ module Resque
808
833
  # machine. Useful when pruning dead workers on startup.
809
834
  def windows_worker_pids
810
835
  tasklist_output = `tasklist /FI "IMAGENAME eq ruby.exe" /FO list`.encode("UTF-8", Encoding.locale_charmap)
811
- tasklist_output.split($/).select { |line| line =~ /^PID:/}.collect{ |line| line.gsub /PID:\s+/, '' }
836
+ tasklist_output.split($/).select { |line| line =~ /^PID:/ }.collect { |line| line.gsub(/PID:\s+/, '') }
812
837
  end
813
838
 
814
839
  # Find Resque worker pids on Linux and OS X.
@@ -827,7 +852,7 @@ module Resque
827
852
  `ps -A -o pid,comm | grep "[r]uby" | grep -v "resque-web"`.split("\n").map do |line|
828
853
  real_pid = line.split(' ')[0]
829
854
  pargs_command = `pargs -a #{real_pid} 2>/dev/null | grep [r]esque | grep -v "resque-web"`
830
- if pargs_command.split(':')[1] == " resque-#{Resque::Version}"
855
+ if pargs_command.split(':')[1] == " resque-#{Resque::VERSION}"
831
856
  real_pid
832
857
  end
833
858
  end.compact
@@ -837,7 +862,7 @@ module Resque
837
862
  # Procline is always in the format of:
838
863
  # RESQUE_PROCLINE_PREFIXresque-VERSION: STRING
839
864
  def procline(string)
840
- $0 = "#{ENV['RESQUE_PROCLINE_PREFIX']}resque-#{Resque::Version}: #{string}"
865
+ $0 = "#{ENV['RESQUE_PROCLINE_PREFIX']}resque-#{Resque::VERSION}: #{string}"
841
866
  log_with_severity :debug, $0
842
867
  end
843
868
 
@@ -850,13 +875,7 @@ module Resque
850
875
  end
851
876
 
852
877
 
853
- def verbose
854
- @verbose
855
- end
856
-
857
- def very_verbose
858
- @very_verbose
859
- end
878
+ attr_reader :verbose, :very_verbose
860
879
 
861
880
  def verbose=(value);
862
881
  if value && !very_verbose
@@ -909,7 +928,7 @@ module Resque
909
928
  nil
910
929
  end
911
930
 
912
- job.fail(DirtyExit.new("Child process received unhandled signal #{$?.stopsig}", $?)) if $?.signaled?
931
+ job.fail(DirtyExit.new("Child process received unhandled signal #{$?}", $?)) if $?.signaled?
913
932
  @child = nil
914
933
  end
915
934