resque 1.26.pre.0 → 1.26.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 +7 -0
- data/HISTORY.md +29 -16
- data/README.markdown +60 -6
- data/Rakefile +4 -17
- data/bin/resque-web +4 -0
- data/lib/resque.rb +116 -16
- data/lib/resque/errors.rb +1 -0
- data/lib/resque/failure.rb +11 -5
- data/lib/resque/failure/multiple.rb +6 -1
- data/lib/resque/failure/redis.rb +13 -4
- data/lib/resque/failure/redis_multi_queue.rb +14 -6
- data/lib/resque/helpers.rb +5 -64
- data/lib/resque/job.rb +25 -79
- data/lib/resque/logging.rb +1 -1
- data/lib/resque/plugin.rb +22 -10
- data/lib/resque/server.rb +35 -7
- data/lib/resque/server/helpers.rb +1 -1
- data/lib/resque/server/views/failed.erb +1 -1
- data/lib/resque/server/views/failed_job.erb +3 -3
- data/lib/resque/server/views/failed_queues_overview.erb +3 -3
- data/lib/resque/server/views/workers.erb +2 -0
- data/lib/resque/server/views/working.erb +4 -5
- data/lib/resque/tasks.rb +7 -25
- data/lib/resque/vendor/utf8_util/utf8_util_19.rb +1 -0
- data/lib/resque/version.rb +1 -1
- data/lib/resque/worker.rb +225 -116
- metadata +68 -90
- data/test/airbrake_test.rb +0 -27
- data/test/dump.rdb +0 -0
- data/test/failure_base_test.rb +0 -15
- data/test/job_hooks_test.rb +0 -464
- data/test/job_plugins_test.rb +0 -230
- data/test/logging_test.rb +0 -24
- data/test/plugin_test.rb +0 -116
- data/test/redis-test-cluster.conf +0 -115
- data/test/redis-test.conf +0 -115
- data/test/resque-web_test.rb +0 -59
- data/test/resque_failure_redis_test.rb +0 -19
- data/test/resque_hook_test.rb +0 -165
- data/test/resque_test.rb +0 -278
- data/test/test_helper.rb +0 -198
- data/test/worker_test.rb +0 -1015
data/lib/resque/server.rb
CHANGED
@@ -38,10 +38,14 @@ module Resque
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def url_path(*path_parts)
|
41
|
-
[ path_prefix, path_parts ].join("/").squeeze('/')
|
41
|
+
[ url_prefix, path_prefix, path_parts ].join("/").squeeze('/')
|
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
|
+
|
45
49
|
def path_prefix
|
46
50
|
request.env['SCRIPT_NAME']
|
47
51
|
end
|
@@ -60,6 +64,10 @@ module Resque
|
|
60
64
|
Resque::Server.tabs
|
61
65
|
end
|
62
66
|
|
67
|
+
def url_prefix
|
68
|
+
Resque::Server.url_prefix
|
69
|
+
end
|
70
|
+
|
63
71
|
def redis_get_size(key)
|
64
72
|
case Resque.redis.type(key)
|
65
73
|
when 'none'
|
@@ -150,7 +158,7 @@ module Resque
|
|
150
158
|
|
151
159
|
# to make things easier on ourselves
|
152
160
|
get "/?" do
|
153
|
-
redirect
|
161
|
+
redirect redirect_url_path(:overview)
|
154
162
|
end
|
155
163
|
|
156
164
|
%w( overview workers ).each do |page|
|
@@ -205,15 +213,13 @@ module Resque
|
|
205
213
|
end
|
206
214
|
|
207
215
|
post "/failed/requeue/all" do
|
208
|
-
Resque::Failure.
|
209
|
-
Resque::Failure.requeue(num)
|
210
|
-
end
|
216
|
+
Resque::Failure.requeue_all
|
211
217
|
redirect u('failed')
|
212
218
|
end
|
213
219
|
|
214
220
|
post "/failed/:queue/requeue/all" do
|
215
221
|
Resque::Failure.requeue_queue Resque::Failure.job_queue_name(params[:queue])
|
216
|
-
redirect
|
222
|
+
redirect redirect_url_path("/failed/#{params[:queue]}")
|
217
223
|
end
|
218
224
|
|
219
225
|
get "/failed/requeue/:index/?" do
|
@@ -225,13 +231,27 @@ module Resque
|
|
225
231
|
end
|
226
232
|
end
|
227
233
|
|
234
|
+
get "/failed/:queue/requeue/:index/?" do
|
235
|
+
Resque::Failure.requeue(params[:index], params[:queue])
|
236
|
+
if request.xhr?
|
237
|
+
return Resque::Failure.all(params[:index],1,params[:queue])['retried_at']
|
238
|
+
else
|
239
|
+
redirect url_path("/failed/#{params[:queue]}")
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
228
243
|
get "/failed/remove/:index/?" do
|
229
244
|
Resque::Failure.remove(params[:index])
|
230
245
|
redirect u('failed')
|
231
246
|
end
|
232
247
|
|
248
|
+
get "/failed/:queue/remove/:index/?" do
|
249
|
+
Resque::Failure.remove(params[:index], params[:queue])
|
250
|
+
redirect url_path("/failed/#{params[:queue]}")
|
251
|
+
end
|
252
|
+
|
233
253
|
get "/stats/?" do
|
234
|
-
redirect
|
254
|
+
redirect redirect_url_path("/stats/resque")
|
235
255
|
end
|
236
256
|
|
237
257
|
get "/stats/:id/?" do
|
@@ -267,5 +287,13 @@ module Resque
|
|
267
287
|
def self.tabs
|
268
288
|
@tabs ||= ["Overview", "Working", "Failed", "Queues", "Workers", "Stats"]
|
269
289
|
end
|
290
|
+
|
291
|
+
def self.url_prefix=(url_prefix)
|
292
|
+
@url_prefix = url_prefix
|
293
|
+
end
|
294
|
+
|
295
|
+
def self.url_prefix
|
296
|
+
(@url_prefix.nil? || @url_prefix.empty?) ? '' : @url_prefix + '/'
|
297
|
+
end
|
270
298
|
end
|
271
299
|
end
|
@@ -21,7 +21,7 @@
|
|
21
21
|
|
22
22
|
<ul class='failed'>
|
23
23
|
<% Resque::Failure.each(failed_start_at, failed_per_page, params[:queue], params[:class], failed_order) do |id, job| %>
|
24
|
-
<%= partial :failed_job, :id => id, :job => job %>
|
24
|
+
<%= partial :failed_job, :id => id, :job => job, :queue => "failed#{'/' + params[:queue] if params[:queue]}" %>
|
25
25
|
<% end %>
|
26
26
|
</ul>
|
27
27
|
|
@@ -10,13 +10,13 @@
|
|
10
10
|
<% if job['retried_at'] %>
|
11
11
|
<div class='retried'>
|
12
12
|
Retried <b><span class="time"><%= Time.parse(job['retried_at']).strftime(failed_date_format) %></span></b>
|
13
|
-
<a href="<%= u "
|
13
|
+
<a href="<%= u "#{queue}/remove/#{id}" %>" class="remove" rel="remove">Remove</a>
|
14
14
|
</div>
|
15
15
|
<% else %>
|
16
16
|
<div class='controls'>
|
17
|
-
<a href="<%= u "
|
17
|
+
<a href="<%= u "#{queue}/requeue/#{id}" %>" rel="retry">Retry</a>
|
18
18
|
or
|
19
|
-
<a href="<%= u "
|
19
|
+
<a href="<%= u "#{queue}/remove/#{id}" %>" rel="remove">Remove</a>
|
20
20
|
</div>
|
21
21
|
<% end %>
|
22
22
|
</dd>
|
@@ -7,18 +7,18 @@
|
|
7
7
|
|
8
8
|
<% Resque::Failure.queues.sort.each do |queue| %>
|
9
9
|
<tr>
|
10
|
-
<th><b class="queue-tag"><a href="/failed
|
10
|
+
<th><b class="queue-tag"><a href="<%= u "/failed/#{queue}" %>"><%= queue %></a></b></th>
|
11
11
|
<th style="width:75px;" class="center"><%= Resque::Failure.count(queue) %></th>
|
12
12
|
</tr>
|
13
13
|
|
14
14
|
<% failed_class_counts(queue).sort_by { |name,_| name }.each do |k, v| %>
|
15
15
|
<tr id="<%= k %>">
|
16
16
|
<td>
|
17
|
-
<a href="/failed
|
17
|
+
<a href="<%= u "/failed/#{queue}?class=#{k}" %>"><span class="failed failed_class"><%= k %></span></a>
|
18
18
|
</td>
|
19
19
|
<td style="text-align: center;" class="failed<%= (v.to_i > 1000) ? '_many' : '' %>"><%= v %></td>
|
20
20
|
</tr>
|
21
21
|
<% end %>
|
22
22
|
<% end %>
|
23
23
|
</tbody>
|
24
|
-
</table>
|
24
|
+
</table>
|
@@ -9,6 +9,7 @@
|
|
9
9
|
<th>Host</th>
|
10
10
|
<th>Pid</th>
|
11
11
|
<th>Started</th>
|
12
|
+
<th>Heartbeat</th>
|
12
13
|
<th>Queues</th>
|
13
14
|
<th>Processed</th>
|
14
15
|
<th>Failed</th>
|
@@ -21,6 +22,7 @@
|
|
21
22
|
<td><%= host %></td>
|
22
23
|
<td><%= pid %></td>
|
23
24
|
<td><span class="time"><%= worker.started %></span></td>
|
25
|
+
<td><span class="time"><%= worker.heartbeat %></span></td>
|
24
26
|
<td class='queues'><%= queues.split(',').map { |q| '<a class="queue-tag" href="' + u("/queues/#{q}") + '">' + q + '</a>'}.join('') %></td>
|
25
27
|
<td><%= worker.processed %></td>
|
26
28
|
<td><%= worker.failed %></td>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<% if params[:id] && (worker = Resque::Worker.find(params[:id])) && worker.job %>
|
1
|
+
<% if params[:id] && (worker = Resque::Worker.find(params[:id])) && (data = worker.job) %>
|
2
2
|
<h1><%= worker %>'s job</h1>
|
3
3
|
|
4
4
|
<table>
|
@@ -14,14 +14,13 @@
|
|
14
14
|
<td><img src="<%=u 'working.png' %>" alt="working" title="working"></td>
|
15
15
|
<% host, pid, _ = worker.to_s.split(':') %>
|
16
16
|
<td><a href="<%=u "/workers/#{worker}" %>"><%= host %>:<%= pid %></a></td>
|
17
|
-
<% data = worker.job %>
|
18
17
|
<% queue = data['queue'] %>
|
19
18
|
<td><a class="queue" href="<%=u "/queues/#{queue}" %>"><%= queue %></a></td>
|
20
19
|
<td><span class="time"><%= data['run_at'] %></span></td>
|
21
20
|
<td>
|
22
|
-
<code><%= data['payload']['class'] %></code>
|
21
|
+
<code><%= data['payload'].to_h['class'] %></code>
|
23
22
|
</td>
|
24
|
-
<td><%=h data['payload']['args'].inspect %></td>
|
23
|
+
<td><%=h data['payload'].to_h['args'].inspect %></td>
|
25
24
|
</tr>
|
26
25
|
</table>
|
27
26
|
|
@@ -49,7 +48,7 @@
|
|
49
48
|
</tr>
|
50
49
|
<% end %>
|
51
50
|
|
52
|
-
<% worker_jobs.sort_by {|w, j| j['run_at'] ? j['run_at'] : '' }.each do |worker, job| %>
|
51
|
+
<% worker_jobs.sort_by {|w, j| j['run_at'] ? j['run_at'].to_s() : '' }.each do |worker, job| %>
|
53
52
|
<tr>
|
54
53
|
<td class='icon'><img src="<%=u state = worker.state %>.png" alt="<%= state %>" title="<%= state %>"></td>
|
55
54
|
<% host, pid, queues = worker.to_s.split(':') %>
|
data/lib/resque/tasks.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# require 'resque/tasks'
|
2
2
|
# will give you the resque tasks
|
3
3
|
|
4
|
+
|
4
5
|
namespace :resque do
|
5
6
|
task :setup
|
6
7
|
|
@@ -8,36 +9,13 @@ namespace :resque do
|
|
8
9
|
task :work => [ :preload, :setup ] do
|
9
10
|
require 'resque'
|
10
11
|
|
11
|
-
queues = (ENV['QUEUES'] || ENV['QUEUE']).to_s.split(',')
|
12
|
-
|
13
12
|
begin
|
14
|
-
worker = Resque::Worker.new
|
15
|
-
if ENV['LOGGING'] || ENV['VERBOSE']
|
16
|
-
worker.verbose = ENV['LOGGING'] || ENV['VERBOSE']
|
17
|
-
end
|
18
|
-
if ENV['VVERBOSE']
|
19
|
-
worker.very_verbose = ENV['VVERBOSE']
|
20
|
-
end
|
21
|
-
worker.term_timeout = ENV['RESQUE_TERM_TIMEOUT'] || 4.0
|
22
|
-
worker.term_child = ENV['TERM_CHILD']
|
23
|
-
worker.run_at_exit_hooks = ENV['RUN_AT_EXIT_HOOKS']
|
13
|
+
worker = Resque::Worker.new
|
24
14
|
rescue Resque::NoQueueError
|
25
15
|
abort "set QUEUE env var, e.g. $ QUEUE=critical,high rake resque:work"
|
26
16
|
end
|
27
17
|
|
28
|
-
|
29
|
-
unless Process.respond_to?('daemon')
|
30
|
-
abort "env var BACKGROUND is set, which requires ruby >= 1.9"
|
31
|
-
end
|
32
|
-
Process.daemon(true, true)
|
33
|
-
end
|
34
|
-
|
35
|
-
if ENV['PIDFILE']
|
36
|
-
File.open(ENV['PIDFILE'], 'w') { |f| f << worker.pid }
|
37
|
-
end
|
38
|
-
|
39
|
-
worker.log "Starting worker #{worker}"
|
40
|
-
|
18
|
+
worker.log "Starting worker #{self}"
|
41
19
|
worker.work(ENV['INTERVAL'] || 5) # interval, will block
|
42
20
|
end
|
43
21
|
|
@@ -45,6 +23,10 @@ namespace :resque do
|
|
45
23
|
task :workers do
|
46
24
|
threads = []
|
47
25
|
|
26
|
+
if ENV['COUNT'].to_i < 1
|
27
|
+
abort "set COUNT env var, e.g. $ COUNT=2 rake resque:workers"
|
28
|
+
end
|
29
|
+
|
48
30
|
ENV['COUNT'].to_i.times do
|
49
31
|
threads << Thread.new do
|
50
32
|
system "rake resque:work"
|
data/lib/resque/version.rb
CHANGED
data/lib/resque/worker.rb
CHANGED
@@ -10,8 +10,12 @@ module Resque
|
|
10
10
|
# It also ensures workers are always listening to signals from you,
|
11
11
|
# their master, and can react accordingly.
|
12
12
|
class Worker
|
13
|
+
include Resque::Helpers
|
14
|
+
extend Resque::Helpers
|
13
15
|
include Resque::Logging
|
14
16
|
|
17
|
+
WORKER_HEARTBEAT_KEY = "workers:heartbeat"
|
18
|
+
|
15
19
|
def redis
|
16
20
|
Resque.redis
|
17
21
|
end
|
@@ -23,46 +27,33 @@ module Resque
|
|
23
27
|
# Given a Ruby object, returns a string suitable for storage in a
|
24
28
|
# queue.
|
25
29
|
def encode(object)
|
26
|
-
|
27
|
-
MultiJson.dump object
|
28
|
-
else
|
29
|
-
MultiJson.encode object
|
30
|
-
end
|
30
|
+
Resque.encode(object)
|
31
31
|
end
|
32
32
|
|
33
33
|
# Given a string, returns a Ruby object.
|
34
34
|
def decode(object)
|
35
|
-
|
36
|
-
|
37
|
-
begin
|
38
|
-
if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
|
39
|
-
MultiJson.load object
|
40
|
-
else
|
41
|
-
MultiJson.decode object
|
42
|
-
end
|
43
|
-
rescue ::MultiJson::DecodeError => e
|
44
|
-
raise DecodeException, e.message, e.backtrace
|
45
|
-
end
|
35
|
+
Resque.decode(object)
|
46
36
|
end
|
47
37
|
|
48
|
-
# Boolean indicating whether this worker can or can not fork.
|
49
|
-
# Automatically set if a fork(2) fails.
|
50
|
-
attr_accessor :cant_fork
|
51
|
-
|
52
38
|
attr_accessor :term_timeout
|
53
39
|
|
54
40
|
# decide whether to use new_kill_child logic
|
55
41
|
attr_accessor :term_child
|
56
42
|
|
43
|
+
# should term kill workers gracefully (vs. immediately)
|
44
|
+
# Makes SIGTERM work like SIGQUIT
|
45
|
+
attr_accessor :graceful_term
|
46
|
+
|
57
47
|
# When set to true, forked workers will exit with `exit`, calling any `at_exit` code handlers that have been
|
58
48
|
# registered in the application. Otherwise, forked workers exit with `exit!`
|
59
49
|
attr_accessor :run_at_exit_hooks
|
60
50
|
|
61
51
|
attr_writer :to_s
|
52
|
+
attr_writer :pid
|
62
53
|
|
63
54
|
# Returns an array of all worker objects.
|
64
55
|
def self.all
|
65
|
-
Array(redis.smembers(:workers)).map { |id| find(id) }.compact
|
56
|
+
Array(redis.smembers(:workers)).map { |id| find(id, :skip_exists => true) }.compact
|
66
57
|
end
|
67
58
|
|
68
59
|
# Returns an array of all worker objects currently processing
|
@@ -87,16 +78,22 @@ module Resque
|
|
87
78
|
end
|
88
79
|
|
89
80
|
reportedly_working.keys.map do |key|
|
90
|
-
find
|
81
|
+
worker = find(key.sub("worker:", ''), :skip_exists => true)
|
82
|
+
worker.job = worker.decode(reportedly_working[key])
|
83
|
+
worker
|
91
84
|
end.compact
|
92
85
|
end
|
93
86
|
|
94
87
|
# Returns a single worker object. Accepts a string id.
|
95
|
-
def self.find(worker_id)
|
96
|
-
|
97
|
-
|
88
|
+
def self.find(worker_id, options = {})
|
89
|
+
skip_exists = options[:skip_exists]
|
90
|
+
|
91
|
+
if skip_exists || exists?(worker_id)
|
92
|
+
host, pid, queues_raw = worker_id.split(':')
|
93
|
+
queues = queues_raw.split(',')
|
98
94
|
worker = new(*queues)
|
99
95
|
worker.to_s = worker_id
|
96
|
+
worker.pid = pid.to_i
|
100
97
|
worker
|
101
98
|
else
|
102
99
|
nil
|
@@ -126,9 +123,38 @@ module Resque
|
|
126
123
|
# in alphabetical order. Queues can be dynamically added or
|
127
124
|
# removed without needing to restart workers using this method.
|
128
125
|
def initialize(*queues)
|
129
|
-
@queues = queues.map { |queue| queue.to_s.strip }
|
130
126
|
@shutdown = nil
|
131
127
|
@paused = nil
|
128
|
+
@before_first_fork_hook_ran = false
|
129
|
+
|
130
|
+
self.verbose = ENV['LOGGING'] || ENV['VERBOSE']
|
131
|
+
self.very_verbose = ENV['VVERBOSE']
|
132
|
+
self.term_timeout = ENV['RESQUE_TERM_TIMEOUT'] || 4.0
|
133
|
+
self.term_child = ENV['TERM_CHILD']
|
134
|
+
self.graceful_term = ENV['GRACEFUL_TERM']
|
135
|
+
self.run_at_exit_hooks = ENV['RUN_AT_EXIT_HOOKS']
|
136
|
+
|
137
|
+
if ENV['BACKGROUND']
|
138
|
+
unless Process.respond_to?('daemon')
|
139
|
+
abort "env var BACKGROUND is set, which requires ruby >= 1.9"
|
140
|
+
end
|
141
|
+
Process.daemon(true)
|
142
|
+
self.reconnect
|
143
|
+
end
|
144
|
+
|
145
|
+
if ENV['PIDFILE']
|
146
|
+
File.open(ENV['PIDFILE'], 'w') { |f| f << pid }
|
147
|
+
end
|
148
|
+
|
149
|
+
self.queues = queues
|
150
|
+
end
|
151
|
+
|
152
|
+
def queues=(queues)
|
153
|
+
queues = queues.empty? ? (ENV["QUEUES"] || ENV['QUEUE']).to_s.split(',') : queues
|
154
|
+
@queues = queues.map { |queue| queue.to_s.strip }
|
155
|
+
unless ['*', '?', '{', '}', '[', ']'].any? {|char| @queues.join.include?(char) }
|
156
|
+
@static_queues = @queues.flatten.uniq
|
157
|
+
end
|
132
158
|
validate_queues
|
133
159
|
end
|
134
160
|
|
@@ -142,6 +168,20 @@ module Resque
|
|
142
168
|
end
|
143
169
|
end
|
144
170
|
|
171
|
+
# Returns a list of queues to use when searching for a job.
|
172
|
+
# A splat ("*") means you want every queue (in alpha order) - this
|
173
|
+
# can be useful for dynamically adding new queues.
|
174
|
+
def queues
|
175
|
+
return @static_queues if @static_queues
|
176
|
+
@queues.map { |queue| glob_match(queue) }.flatten.uniq
|
177
|
+
end
|
178
|
+
|
179
|
+
def glob_match(pattern)
|
180
|
+
Resque.queues.select do |queue|
|
181
|
+
File.fnmatch?(pattern, queue)
|
182
|
+
end.sort
|
183
|
+
end
|
184
|
+
|
145
185
|
# This is the main workhorse method. Called on a Worker instance,
|
146
186
|
# it begins the worker life cycle.
|
147
187
|
#
|
@@ -167,7 +207,7 @@ module Resque
|
|
167
207
|
break if shutdown?
|
168
208
|
|
169
209
|
if not paused? and job = reserve
|
170
|
-
|
210
|
+
log_with_severity :info, "got: #{job.inspect}"
|
171
211
|
job.worker = self
|
172
212
|
working_on job
|
173
213
|
|
@@ -180,12 +220,12 @@ module Resque
|
|
180
220
|
rescue SystemCallError
|
181
221
|
nil
|
182
222
|
end
|
183
|
-
job.fail(DirtyExit.new($?.
|
223
|
+
job.fail(DirtyExit.new("Child process received unhandled signal #{$?.stopsig}")) if $?.signaled?
|
184
224
|
else
|
185
225
|
unregister_signal_handlers if will_fork? && term_child
|
186
226
|
begin
|
187
227
|
|
188
|
-
reconnect
|
228
|
+
reconnect if will_fork?
|
189
229
|
perform(job, &block)
|
190
230
|
|
191
231
|
rescue Exception => exception
|
@@ -201,8 +241,8 @@ module Resque
|
|
201
241
|
@child = nil
|
202
242
|
else
|
203
243
|
break if interval.zero?
|
204
|
-
|
205
|
-
procline paused? ? "Paused" : "Waiting for #{
|
244
|
+
log_with_severity :debug, "Sleeping for #{interval} seconds"
|
245
|
+
procline paused? ? "Paused" : "Waiting for #{queues.join(',')}"
|
206
246
|
sleep interval
|
207
247
|
end
|
208
248
|
end
|
@@ -210,7 +250,7 @@ module Resque
|
|
210
250
|
unregister_worker
|
211
251
|
rescue Exception => exception
|
212
252
|
unless exception.class == SystemExit && !@child && run_at_exit_hooks
|
213
|
-
|
253
|
+
log_with_severity :error, "Failed to start worker : #{exception.inspect}"
|
214
254
|
|
215
255
|
unregister_worker(exception)
|
216
256
|
end
|
@@ -230,16 +270,16 @@ module Resque
|
|
230
270
|
|
231
271
|
# Reports the exception and marks the job as failed
|
232
272
|
def report_failed_job(job,exception)
|
233
|
-
|
273
|
+
log_with_severity :error, "#{job.inspect} failed: #{exception.inspect}"
|
234
274
|
begin
|
235
275
|
job.fail(exception)
|
236
276
|
rescue Object => exception
|
237
|
-
|
277
|
+
log_with_severity :error, "Received exception when reporting failure: #{exception.inspect}"
|
238
278
|
end
|
239
279
|
begin
|
240
280
|
failed!
|
241
281
|
rescue Object => exception
|
242
|
-
|
282
|
+
log_with_severity :error, "Received exception when increasing failed jobs counter (redis issue) : #{exception.inspect}"
|
243
283
|
end
|
244
284
|
end
|
245
285
|
|
@@ -251,7 +291,7 @@ module Resque
|
|
251
291
|
rescue Object => e
|
252
292
|
report_failed_job(job,e)
|
253
293
|
else
|
254
|
-
|
294
|
+
log_with_severity :info, "done: #{job.inspect}"
|
255
295
|
ensure
|
256
296
|
yield job if block_given?
|
257
297
|
end
|
@@ -261,17 +301,17 @@ module Resque
|
|
261
301
|
# nil if no job can be found.
|
262
302
|
def reserve
|
263
303
|
queues.each do |queue|
|
264
|
-
|
304
|
+
log_with_severity :debug, "Checking #{queue}"
|
265
305
|
if job = Resque.reserve(queue)
|
266
|
-
|
306
|
+
log_with_severity :debug, "Found job on #{queue}"
|
267
307
|
return job
|
268
308
|
end
|
269
309
|
end
|
270
310
|
|
271
311
|
nil
|
272
312
|
rescue Exception => e
|
273
|
-
|
274
|
-
|
313
|
+
log_with_severity :error, "Error reserving job: #{e.inspect}"
|
314
|
+
log_with_severity :error, e.backtrace.join("\n")
|
275
315
|
raise e
|
276
316
|
end
|
277
317
|
|
@@ -283,49 +323,29 @@ module Resque
|
|
283
323
|
redis.client.reconnect
|
284
324
|
rescue Redis::BaseConnectionError
|
285
325
|
if (tries += 1) <= 3
|
286
|
-
|
326
|
+
log_with_severity :error, "Error reconnecting to Redis; retrying"
|
287
327
|
sleep(tries)
|
288
328
|
retry
|
289
329
|
else
|
290
|
-
|
330
|
+
log_with_severity :error, "Error reconnecting to Redis; quitting"
|
291
331
|
raise
|
292
332
|
end
|
293
333
|
end
|
294
334
|
end
|
295
335
|
|
296
|
-
# Returns a list of queues to use when searching for a job.
|
297
|
-
# A splat ("*") means you want every queue (in alpha order) - this
|
298
|
-
# can be useful for dynamically adding new queues.
|
299
|
-
def queues
|
300
|
-
@queues.map do |queue|
|
301
|
-
queue.strip!
|
302
|
-
if (matched_queues = glob_match(queue)).empty?
|
303
|
-
queue
|
304
|
-
else
|
305
|
-
matched_queues
|
306
|
-
end
|
307
|
-
end.flatten.uniq
|
308
|
-
end
|
309
|
-
|
310
|
-
def glob_match(pattern)
|
311
|
-
Resque.queues.select do |queue|
|
312
|
-
File.fnmatch?(pattern, queue)
|
313
|
-
end.sort
|
314
|
-
end
|
315
|
-
|
316
336
|
# Not every platform supports fork. Here we do our magic to
|
317
337
|
# determine if yours does.
|
318
338
|
def fork(job)
|
319
|
-
return
|
339
|
+
return unless will_fork?
|
320
340
|
|
321
341
|
# Only run before_fork hooks if we're actually going to fork
|
322
|
-
# (after checking
|
342
|
+
# (after checking will_fork?)
|
323
343
|
run_hook :before_fork, job
|
324
344
|
|
325
345
|
begin
|
326
346
|
# IronRuby doesn't support `Kernel.fork` yet
|
327
347
|
if Kernel.respond_to?(:fork)
|
328
|
-
Kernel.fork
|
348
|
+
Kernel.fork
|
329
349
|
else
|
330
350
|
raise NotImplementedError
|
331
351
|
end
|
@@ -337,9 +357,9 @@ module Resque
|
|
337
357
|
|
338
358
|
# Runs all the methods needed when a worker begins its lifecycle.
|
339
359
|
def startup
|
340
|
-
Kernel.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 or $TESTING
|
341
360
|
enable_gc_optimizations
|
342
361
|
register_signal_handlers
|
362
|
+
start_heartbeat
|
343
363
|
prune_dead_workers
|
344
364
|
run_hook :before_first_fork
|
345
365
|
register_worker
|
@@ -366,7 +386,7 @@ module Resque
|
|
366
386
|
# USR2: Don't process any new jobs
|
367
387
|
# CONT: Start processing jobs again after a USR2
|
368
388
|
def register_signal_handlers
|
369
|
-
trap('TERM') { shutdown! }
|
389
|
+
trap('TERM') { graceful_term ? shutdown : shutdown! }
|
370
390
|
trap('INT') { shutdown! }
|
371
391
|
|
372
392
|
begin
|
@@ -379,19 +399,19 @@ module Resque
|
|
379
399
|
trap('USR2') { pause_processing }
|
380
400
|
trap('CONT') { unpause_processing }
|
381
401
|
rescue ArgumentError
|
382
|
-
warn "Signals QUIT, USR1, USR2, and/or CONT not supported."
|
402
|
+
log_with_severity :warn, "Signals QUIT, USR1, USR2, and/or CONT not supported."
|
383
403
|
end
|
384
404
|
|
385
|
-
|
405
|
+
log_with_severity :debug, "Registered signals"
|
386
406
|
end
|
387
407
|
|
388
408
|
def unregister_signal_handlers
|
389
409
|
trap('TERM') do
|
390
|
-
trap ('TERM') do
|
391
|
-
# ignore subsequent terms
|
392
|
-
end
|
393
|
-
raise TermException.new("SIGTERM")
|
394
|
-
end
|
410
|
+
trap ('TERM') do
|
411
|
+
# ignore subsequent terms
|
412
|
+
end
|
413
|
+
raise TermException.new("SIGTERM")
|
414
|
+
end
|
395
415
|
trap('INT', 'DEFAULT')
|
396
416
|
|
397
417
|
begin
|
@@ -405,15 +425,24 @@ module Resque
|
|
405
425
|
# Schedule this worker for shutdown. Will finish processing the
|
406
426
|
# current job.
|
407
427
|
def shutdown
|
408
|
-
|
428
|
+
log_with_severity :info, 'Exiting...'
|
409
429
|
@shutdown = true
|
410
430
|
end
|
411
431
|
|
412
432
|
# Kill the child and shutdown immediately.
|
433
|
+
# If not forking, abort this process.
|
413
434
|
def shutdown!
|
414
435
|
shutdown
|
415
436
|
if term_child
|
416
|
-
|
437
|
+
if fork_per_job?
|
438
|
+
new_kill_child
|
439
|
+
else
|
440
|
+
# Raise TermException in the same process
|
441
|
+
trap('TERM') do
|
442
|
+
# ignore subsequent terms
|
443
|
+
end
|
444
|
+
raise TermException.new("SIGTERM")
|
445
|
+
end
|
417
446
|
else
|
418
447
|
kill_child
|
419
448
|
end
|
@@ -428,36 +457,78 @@ module Resque
|
|
428
457
|
# is processing will not be completed.
|
429
458
|
def kill_child
|
430
459
|
if @child
|
431
|
-
|
460
|
+
log_with_severity :debug, "Killing child at #{@child}"
|
432
461
|
if `ps -o pid,state -p #{@child}`
|
433
462
|
Process.kill("KILL", @child) rescue nil
|
434
463
|
else
|
435
|
-
|
464
|
+
log_with_severity :debug, "Child #{@child} not found, restarting."
|
436
465
|
shutdown
|
437
466
|
end
|
438
467
|
end
|
439
468
|
end
|
440
469
|
|
470
|
+
def heartbeat
|
471
|
+
heartbeat = redis.hget(WORKER_HEARTBEAT_KEY, to_s)
|
472
|
+
heartbeat && Time.parse(heartbeat)
|
473
|
+
end
|
474
|
+
|
475
|
+
def self.all_heartbeats
|
476
|
+
redis.hgetall(WORKER_HEARTBEAT_KEY)
|
477
|
+
end
|
478
|
+
|
479
|
+
# Returns a list of workers that have sent a heartbeat in the past, but which
|
480
|
+
# already expired (does NOT include workers that have never sent a heartbeat at all).
|
481
|
+
def self.all_workers_with_expired_heartbeats
|
482
|
+
workers = Worker.all
|
483
|
+
heartbeats = Worker.all_heartbeats
|
484
|
+
|
485
|
+
workers.select do |worker|
|
486
|
+
id = worker.to_s
|
487
|
+
heartbeat = heartbeats[id]
|
488
|
+
|
489
|
+
if heartbeat
|
490
|
+
seconds_since_heartbeat = (Time.now - Time.parse(heartbeat)).to_i
|
491
|
+
seconds_since_heartbeat > Resque.prune_interval
|
492
|
+
else
|
493
|
+
false
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
def heartbeat!(time = Time.now)
|
499
|
+
redis.hset(WORKER_HEARTBEAT_KEY, to_s, time.iso8601)
|
500
|
+
end
|
501
|
+
|
502
|
+
def start_heartbeat
|
503
|
+
heartbeat!
|
504
|
+
@heart = Thread.new do
|
505
|
+
loop do
|
506
|
+
sleep(Resque.heartbeat_interval)
|
507
|
+
heartbeat!
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
441
512
|
# Kills the forked child immediately with minimal remorse. The job it
|
442
513
|
# is processing will not be completed. Send the child a TERM signal,
|
443
514
|
# wait 5 seconds, and then a KILL signal if it has not quit
|
444
515
|
def new_kill_child
|
445
516
|
if @child
|
446
517
|
unless Process.waitpid(@child, Process::WNOHANG)
|
447
|
-
|
518
|
+
log_with_severity :debug, "Sending TERM signal to child #{@child}"
|
448
519
|
Process.kill("TERM", @child)
|
449
520
|
(term_timeout.to_f * 10).round.times do |i|
|
450
521
|
sleep(0.1)
|
451
522
|
return if Process.waitpid(@child, Process::WNOHANG)
|
452
523
|
end
|
453
|
-
|
524
|
+
log_with_severity :debug, "Sending KILL signal to child #{@child}"
|
454
525
|
Process.kill("KILL", @child)
|
455
526
|
else
|
456
|
-
|
527
|
+
log_with_severity :debug, "Child #{@child} already quit."
|
457
528
|
end
|
458
529
|
end
|
459
530
|
rescue SystemCallError
|
460
|
-
|
531
|
+
log_with_severity :error, "Child #{@child} already quit and reaped."
|
461
532
|
end
|
462
533
|
|
463
534
|
# are we paused?
|
@@ -468,14 +539,16 @@ module Resque
|
|
468
539
|
# Stop processing jobs after the current one has completed (if we're
|
469
540
|
# currently running one).
|
470
541
|
def pause_processing
|
471
|
-
|
542
|
+
log_with_severity :info, "USR2 received; pausing job processing"
|
543
|
+
run_hook :before_pause, self
|
472
544
|
@paused = true
|
473
545
|
end
|
474
546
|
|
475
547
|
# Start processing jobs again after a pause
|
476
548
|
def unpause_processing
|
477
|
-
|
549
|
+
log_with_severity :info, "CONT received; resuming job processing"
|
478
550
|
@paused = false
|
551
|
+
run_hook :after_pause, self
|
479
552
|
end
|
480
553
|
|
481
554
|
# Looks for any workers which should be running on this server
|
@@ -490,21 +563,40 @@ module Resque
|
|
490
563
|
# environment, we can determine if Redis is old and clean it up a bit.
|
491
564
|
def prune_dead_workers
|
492
565
|
all_workers = Worker.all
|
493
|
-
|
566
|
+
|
567
|
+
unless all_workers.empty?
|
568
|
+
known_workers = worker_pids
|
569
|
+
all_workers_with_expired_heartbeats = Worker.all_workers_with_expired_heartbeats
|
570
|
+
end
|
571
|
+
|
494
572
|
all_workers.each do |worker|
|
573
|
+
# If the worker hasn't sent a heartbeat, remove it from the registry.
|
574
|
+
#
|
575
|
+
# If the worker hasn't ever sent a heartbeat, we won't remove it since
|
576
|
+
# the first heartbeat is sent before the worker is registred it means
|
577
|
+
# that this is a worker that doesn't support heartbeats, e.g., another
|
578
|
+
# client library or an older version of Resque. We won't touch these.
|
579
|
+
if all_workers_with_expired_heartbeats.include?(worker)
|
580
|
+
log_with_severity :info, "Pruning dead worker: #{worker}"
|
581
|
+
worker.unregister_worker(PruneDeadWorkerDirtyExit.new(worker.to_s))
|
582
|
+
next
|
583
|
+
end
|
584
|
+
|
495
585
|
host, pid, worker_queues_raw = worker.id.split(':')
|
496
586
|
worker_queues = worker_queues_raw.split(",")
|
497
587
|
unless @queues.include?("*") || (worker_queues.to_set == @queues.to_set)
|
498
588
|
# If the worker we are trying to prune does not belong to the queues
|
499
|
-
# we are listening to, we should not touch it.
|
589
|
+
# we are listening to, we should not touch it.
|
500
590
|
# Attempt to prune a worker from different queues may easily result in
|
501
|
-
# an unknown class exception, since that worker could easily be even
|
591
|
+
# an unknown class exception, since that worker could easily be even
|
502
592
|
# written in different language.
|
503
593
|
next
|
504
594
|
end
|
595
|
+
|
505
596
|
next unless host == hostname
|
506
597
|
next if known_workers.include?(pid)
|
507
|
-
|
598
|
+
|
599
|
+
log_with_severity :debug, "Pruning dead worker: #{worker}"
|
508
600
|
worker.unregister_worker
|
509
601
|
end
|
510
602
|
end
|
@@ -521,15 +613,21 @@ module Resque
|
|
521
613
|
# Runs a named hook, passing along any arguments.
|
522
614
|
def run_hook(name, *args)
|
523
615
|
return unless hooks = Resque.send(name)
|
616
|
+
return if name == :before_first_fork && @before_first_fork_hook_ran
|
524
617
|
msg = "Running #{name} hooks"
|
525
618
|
msg << " with #{args.inspect}" if args.any?
|
526
|
-
|
619
|
+
log_with_severity :info, msg
|
527
620
|
|
528
621
|
hooks.each do |hook|
|
529
622
|
args.any? ? hook.call(*args) : hook.call
|
623
|
+
@before_first_fork_hook_ran = true if name == :before_first_fork
|
530
624
|
end
|
531
625
|
end
|
532
626
|
|
627
|
+
def kill_background_threads
|
628
|
+
@heart.kill if @heart
|
629
|
+
end
|
630
|
+
|
533
631
|
# Unregisters ourself as a worker. Useful when shutting down.
|
534
632
|
def unregister_worker(exception = nil)
|
535
633
|
# If we're still processing a job, make sure it gets logged as a
|
@@ -539,17 +637,33 @@ module Resque
|
|
539
637
|
# Ensure the proper worker is attached to this job, even if
|
540
638
|
# it's not the precise instance that died.
|
541
639
|
job.worker = self
|
542
|
-
|
640
|
+
begin
|
641
|
+
job.fail(exception || DirtyExit.new("Job still being processed"))
|
642
|
+
rescue RuntimeError => e
|
643
|
+
log_with_severity :error, e.message
|
644
|
+
end
|
543
645
|
end
|
544
646
|
|
647
|
+
kill_background_threads
|
648
|
+
|
545
649
|
redis.pipelined do
|
546
650
|
redis.srem(:workers, self)
|
547
651
|
redis.del("worker:#{self}")
|
548
652
|
redis.del("worker:#{self}:started")
|
653
|
+
redis.hdel(WORKER_HEARTBEAT_KEY, self.to_s)
|
549
654
|
|
550
655
|
Stat.clear("processed:#{self}")
|
551
656
|
Stat.clear("failed:#{self}")
|
552
657
|
end
|
658
|
+
rescue Exception => exception_while_unregistering
|
659
|
+
message = exception_while_unregistering.message
|
660
|
+
if exception
|
661
|
+
message = message + "\nOriginal Exception (#{exception.class}): #{exception.message}\n" +
|
662
|
+
" #{exception.backtrace.join(" \n")}"
|
663
|
+
end
|
664
|
+
fail(exception_while_unregistering.class,
|
665
|
+
message,
|
666
|
+
exception_while_unregistering.backtrace)
|
553
667
|
end
|
554
668
|
|
555
669
|
# Given a job, tells Redis we're working on it. Useful for seeing
|
@@ -604,9 +718,11 @@ module Resque
|
|
604
718
|
end
|
605
719
|
|
606
720
|
# Returns a hash explaining the Job we're currently processing, if any.
|
607
|
-
def job
|
608
|
-
|
721
|
+
def job(reload = true)
|
722
|
+
@job = nil if reload
|
723
|
+
@job ||= decode(redis.get("worker:#{self}")) || {}
|
609
724
|
end
|
725
|
+
attr_writer :job
|
610
726
|
alias_method :processing, :job
|
611
727
|
|
612
728
|
# Boolean - true if working, false if not
|
@@ -620,7 +736,11 @@ module Resque
|
|
620
736
|
end
|
621
737
|
|
622
738
|
def will_fork?
|
623
|
-
!@cant_fork &&
|
739
|
+
!@cant_fork && fork_per_job?
|
740
|
+
end
|
741
|
+
|
742
|
+
def fork_per_job?
|
743
|
+
ENV["FORK_PER_JOB"] != 'false'
|
624
744
|
end
|
625
745
|
|
626
746
|
# Returns a symbol representing the current worker state,
|
@@ -677,7 +797,7 @@ module Resque
|
|
677
797
|
# Find Resque worker pids on Linux and OS X.
|
678
798
|
#
|
679
799
|
def linux_worker_pids
|
680
|
-
`ps -A -o pid,command | grep "[r]esque" | grep -v "resque-web"`.split("\n").map do |line|
|
800
|
+
`ps -A -o pid,command | grep -E "[r]esque:work|[r]esque-[0-9]" | grep -v "resque-web"`.split("\n").map do |line|
|
681
801
|
line.split(' ')[0]
|
682
802
|
end
|
683
803
|
end
|
@@ -698,14 +818,12 @@ module Resque
|
|
698
818
|
|
699
819
|
# Given a string, sets the procline ($0) and logs.
|
700
820
|
# Procline is always in the format of:
|
701
|
-
#
|
821
|
+
# RESQUE_PROCLINE_PREFIXresque-VERSION: STRING
|
702
822
|
def procline(string)
|
703
|
-
$0 = "resque-#{Resque::Version}: #{string}"
|
704
|
-
|
823
|
+
$0 = "#{ENV['RESQUE_PROCLINE_PREFIX']}resque-#{Resque::Version}: #{string}"
|
824
|
+
log_with_severity :debug, $0
|
705
825
|
end
|
706
826
|
|
707
|
-
# Log a message to Resque.logger
|
708
|
-
# can't use alias_method since info/debug are private methods
|
709
827
|
def log(message)
|
710
828
|
info(message)
|
711
829
|
end
|
@@ -714,26 +832,19 @@ module Resque
|
|
714
832
|
debug(message)
|
715
833
|
end
|
716
834
|
|
717
|
-
|
718
|
-
# Use Resque.logger.level now, e.g.:
|
719
|
-
#
|
720
|
-
# Resque.logger.level = Logger::DEBUG
|
721
|
-
#
|
835
|
+
|
722
836
|
def verbose
|
723
|
-
logger_severity_deprecation_warning
|
724
837
|
@verbose
|
725
838
|
end
|
726
839
|
|
727
840
|
def very_verbose
|
728
|
-
logger_severity_deprecation_warning
|
729
841
|
@very_verbose
|
730
842
|
end
|
731
843
|
|
732
844
|
def verbose=(value);
|
733
|
-
logger_severity_deprecation_warning
|
734
|
-
|
735
845
|
if value && !very_verbose
|
736
846
|
Resque.logger.formatter = VerboseFormatter.new
|
847
|
+
Resque.logger.level = Logger::INFO
|
737
848
|
elsif !value
|
738
849
|
Resque.logger.formatter = QuietFormatter.new
|
739
850
|
end
|
@@ -742,11 +853,12 @@ module Resque
|
|
742
853
|
end
|
743
854
|
|
744
855
|
def very_verbose=(value)
|
745
|
-
logger_severity_deprecation_warning
|
746
856
|
if value
|
747
857
|
Resque.logger.formatter = VeryVerboseFormatter.new
|
858
|
+
Resque.logger.level = Logger::DEBUG
|
748
859
|
elsif !value && verbose
|
749
860
|
Resque.logger.formatter = VerboseFormatter.new
|
861
|
+
Resque.logger.level = Logger::INFO
|
750
862
|
else
|
751
863
|
Resque.logger.formatter = QuietFormatter.new
|
752
864
|
end
|
@@ -754,13 +866,10 @@ module Resque
|
|
754
866
|
@very_verbose = value
|
755
867
|
end
|
756
868
|
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
Kernel.warn "Called from: #{caller[0..5].join("\n\t")}"
|
762
|
-
$warned_logger_severity_deprecation = true
|
763
|
-
nil
|
869
|
+
private
|
870
|
+
|
871
|
+
def log_with_severity(severity, message)
|
872
|
+
Logging.log(severity, message)
|
764
873
|
end
|
765
874
|
end
|
766
875
|
end
|