resque 1.27.4 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/HISTORY.md +122 -3
- data/README.markdown +441 -500
- data/bin/resque-web +10 -26
- data/lib/resque/data_store.rb +52 -58
- data/lib/resque/errors.rb +7 -1
- data/lib/resque/failure/airbrake.rb +19 -7
- data/lib/resque/failure/multiple.rb +6 -2
- data/lib/resque/failure/redis.rb +1 -1
- data/lib/resque/failure/redis_multi_queue.rb +1 -1
- data/lib/resque/failure.rb +7 -0
- data/lib/resque/job.rb +2 -2
- data/lib/resque/logging.rb +1 -1
- data/lib/resque/railtie.rb +10 -0
- data/lib/resque/server/public/jquery-3.6.0.min.js +2 -0
- data/lib/resque/server/public/main.js +3 -0
- data/lib/resque/server/public/ranger.js +7 -4
- data/lib/resque/server/public/style.css +3 -3
- data/lib/resque/server/views/error.erb +1 -1
- data/lib/resque/server/views/failed.erb +9 -3
- data/lib/resque/server/views/failed_job.erb +2 -2
- data/lib/resque/server/views/job_class.erb +3 -1
- data/lib/resque/server/views/key_string.erb +1 -1
- data/lib/resque/server/views/layout.erb +5 -4
- data/lib/resque/server/views/next_more.erb +14 -14
- data/lib/resque/server/views/queues.erb +6 -6
- data/lib/resque/server/views/stats.erb +5 -5
- data/lib/resque/server/views/working.erb +7 -7
- data/lib/resque/server.rb +11 -119
- data/lib/resque/server_helper.rb +185 -0
- data/lib/resque/stat.rb +16 -9
- data/lib/resque/tasks.rb +3 -11
- data/lib/resque/thread_signal.rb +13 -34
- data/lib/resque/vendor/utf8_util.rb +2 -8
- data/lib/resque/version.rb +1 -1
- data/lib/resque/web_runner.rb +374 -0
- data/lib/resque/worker.rb +76 -48
- data/lib/resque.rb +100 -27
- data/lib/tasks/redis.rake +10 -10
- metadata +44 -28
- data/lib/resque/server/helpers.rb +0 -64
- data/lib/resque/server/public/jquery-1.12.4.min.js +0 -5
- data/lib/resque/server/test_helper.rb +0 -19
- data/lib/resque/vendor/utf8_util/utf8_util_18.rb +0 -91
- data/lib/resque/vendor/utf8_util/utf8_util_19.rb +0 -6
@@ -0,0 +1,374 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'logger'
|
3
|
+
require 'optparse'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'rack'
|
6
|
+
require 'resque/server'
|
7
|
+
|
8
|
+
# only used with `bin/resque-web`
|
9
|
+
# https://github.com/resque/resque/pull/1780
|
10
|
+
|
11
|
+
module Resque
|
12
|
+
WINDOWS = !!(RUBY_PLATFORM =~ /(mingw|bccwin|wince|mswin32)/i)
|
13
|
+
JRUBY = !!(RbConfig::CONFIG["RUBY_INSTALL_NAME"] =~ /^jruby/i)
|
14
|
+
|
15
|
+
class WebRunner
|
16
|
+
attr_reader :app, :app_name, :filesystem_friendly_app_name,
|
17
|
+
:rack_handler, :port, :options, :args
|
18
|
+
|
19
|
+
PORT = 5678
|
20
|
+
HOST = WINDOWS ? 'localhost' : '0.0.0.0'
|
21
|
+
|
22
|
+
def initialize(*runtime_args)
|
23
|
+
@options = runtime_args.last.is_a?(Hash) ? runtime_args.pop : {}
|
24
|
+
|
25
|
+
self.class.logger.level = options[:debug] ? Logger::DEBUG : Logger::INFO
|
26
|
+
|
27
|
+
@app = Resque::Server
|
28
|
+
@app_name = 'resque-web'
|
29
|
+
@filesystem_friendly_app_name = @app_name.gsub(/\W+/, "_")
|
30
|
+
|
31
|
+
@args = load_options(runtime_args)
|
32
|
+
|
33
|
+
@rack_handler = (s = options[:rack_handler]) ? Rack::Handler.get(s) : setup_rack_handler
|
34
|
+
|
35
|
+
case option_parser.command
|
36
|
+
when :help
|
37
|
+
puts option_parser
|
38
|
+
when :kill
|
39
|
+
kill!
|
40
|
+
when :status
|
41
|
+
status
|
42
|
+
when :version
|
43
|
+
puts "resque #{Resque::VERSION}"
|
44
|
+
puts "rack #{Rack::VERSION.join('.')}"
|
45
|
+
puts "sinatra #{Sinatra::VERSION}" if defined?(Sinatra)
|
46
|
+
else
|
47
|
+
before_run
|
48
|
+
start unless options[:start] == false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def launch_path
|
53
|
+
if options[:launch_path].respond_to?(:call)
|
54
|
+
options[:launch_path].call(self)
|
55
|
+
else
|
56
|
+
options[:launch_path]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def app_dir
|
61
|
+
if !options[:app_dir] && !ENV['HOME']
|
62
|
+
raise ArgumentError.new("nor --app-dir neither ENV['HOME'] defined")
|
63
|
+
end
|
64
|
+
options[:app_dir] || File.join(ENV['HOME'], filesystem_friendly_app_name)
|
65
|
+
end
|
66
|
+
|
67
|
+
def pid_file
|
68
|
+
options[:pid_file] || File.join(app_dir, "#{filesystem_friendly_app_name}.pid")
|
69
|
+
end
|
70
|
+
|
71
|
+
def url_file
|
72
|
+
options[:url_file] || File.join(app_dir, "#{filesystem_friendly_app_name}.url")
|
73
|
+
end
|
74
|
+
|
75
|
+
def log_file
|
76
|
+
options[:log_file] || File.join(app_dir, "#{filesystem_friendly_app_name}.log")
|
77
|
+
end
|
78
|
+
|
79
|
+
def host
|
80
|
+
options.fetch(:host) { HOST }
|
81
|
+
end
|
82
|
+
|
83
|
+
def url
|
84
|
+
"http://#{host}:#{port}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def before_run
|
88
|
+
if (redis_conf = options[:redis_conf])
|
89
|
+
logger.info "Using Redis connection '#{redis_conf}'"
|
90
|
+
Resque.redis = redis_conf
|
91
|
+
end
|
92
|
+
if (namespace = options[:redis_namespace])
|
93
|
+
logger.info "Using Redis namespace '#{namespace}'"
|
94
|
+
Resque.redis.namespace = namespace
|
95
|
+
end
|
96
|
+
if (url_prefix = options[:url_prefix])
|
97
|
+
logger.info "Using URL Prefix '#{url_prefix}'"
|
98
|
+
Resque::Server.url_prefix = url_prefix
|
99
|
+
end
|
100
|
+
app.set(options.merge web_runner: self)
|
101
|
+
path = (ENV['RESQUECONFIG'] || args.first)
|
102
|
+
load_config_file(path.to_s.strip) if path
|
103
|
+
end
|
104
|
+
|
105
|
+
def start(path = launch_path)
|
106
|
+
logger.info "Running with Windows Settings" if WINDOWS
|
107
|
+
logger.info "Running with JRuby" if JRUBY
|
108
|
+
logger.info "Starting '#{app_name}'..."
|
109
|
+
|
110
|
+
check_for_running(path)
|
111
|
+
find_port
|
112
|
+
write_url
|
113
|
+
launch!(url, path)
|
114
|
+
daemonize! unless options[:foreground]
|
115
|
+
run!
|
116
|
+
rescue RuntimeError => e
|
117
|
+
logger.warn "There was an error starting '#{app_name}': #{e}"
|
118
|
+
exit
|
119
|
+
end
|
120
|
+
|
121
|
+
def find_port
|
122
|
+
if @port = options[:port]
|
123
|
+
announce_port_attempted
|
124
|
+
|
125
|
+
unless port_open?
|
126
|
+
logger.warn "Port #{port} is already in use. Please try another. " +
|
127
|
+
"You can also omit the port flag, and we'll find one for you."
|
128
|
+
end
|
129
|
+
else
|
130
|
+
@port = PORT
|
131
|
+
announce_port_attempted
|
132
|
+
|
133
|
+
until port_open?
|
134
|
+
@port += 1
|
135
|
+
announce_port_attempted
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def announce_port_attempted
|
141
|
+
logger.info "trying port #{port}..."
|
142
|
+
end
|
143
|
+
|
144
|
+
def port_open?(check_url = nil)
|
145
|
+
begin
|
146
|
+
check_url ||= url
|
147
|
+
options[:no_proxy] ? uri_open(check_url, :proxy => nil) : uri_open(check_url)
|
148
|
+
false
|
149
|
+
rescue Errno::ECONNREFUSED, Errno::EPERM, Errno::ETIMEDOUT
|
150
|
+
true
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def uri_open(*args)
|
155
|
+
(RbConfig::CONFIG['ruby_version'] < '2.7') ? open(*args) : URI.open(*args)
|
156
|
+
end
|
157
|
+
|
158
|
+
def write_url
|
159
|
+
# Make sure app dir is setup
|
160
|
+
FileUtils.mkdir_p(app_dir)
|
161
|
+
File.open(url_file, 'w') {|f| f << url }
|
162
|
+
end
|
163
|
+
|
164
|
+
def check_for_running(path = nil)
|
165
|
+
if File.exist?(pid_file) && File.exist?(url_file)
|
166
|
+
running_url = File.read(url_file)
|
167
|
+
if !port_open?(running_url)
|
168
|
+
logger.warn "'#{app_name}' is already running at #{running_url}"
|
169
|
+
launch!(running_url, path)
|
170
|
+
exit!(1)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def run!
|
176
|
+
logger.info "Running with Rack handler: #{@rack_handler.inspect}"
|
177
|
+
|
178
|
+
rack_handler.run app, :Host => host, :Port => port do |server|
|
179
|
+
kill_commands.each do |command|
|
180
|
+
trap(command) do
|
181
|
+
## Use thins' hard #stop! if available, otherwise just #stop
|
182
|
+
server.respond_to?(:stop!) ? server.stop! : server.stop
|
183
|
+
logger.info "'#{app_name}' received INT ... stopping"
|
184
|
+
delete_pid!
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# Adapted from Rackup
|
191
|
+
def daemonize!
|
192
|
+
if JRUBY
|
193
|
+
# It's not a true daemon but when executed with & works like one
|
194
|
+
thread = Thread.new {daemon_execute}
|
195
|
+
thread.join
|
196
|
+
|
197
|
+
elsif RUBY_VERSION < "1.9"
|
198
|
+
logger.debug "Parent Process: #{Process.pid}"
|
199
|
+
exit!(0) if fork
|
200
|
+
logger.debug "Child Process: #{Process.pid}"
|
201
|
+
daemon_execute
|
202
|
+
|
203
|
+
else
|
204
|
+
Process.daemon(true, true)
|
205
|
+
daemon_execute
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def daemon_execute
|
210
|
+
File.umask 0000
|
211
|
+
FileUtils.touch log_file
|
212
|
+
STDIN.reopen log_file
|
213
|
+
STDOUT.reopen log_file, "a"
|
214
|
+
STDERR.reopen log_file, "a"
|
215
|
+
|
216
|
+
logger.debug "Child Process: #{Process.pid}"
|
217
|
+
|
218
|
+
File.open(pid_file, 'w') {|f| f.write("#{Process.pid}") }
|
219
|
+
at_exit { delete_pid! }
|
220
|
+
end
|
221
|
+
|
222
|
+
def launch!(specific_url = nil, path = nil)
|
223
|
+
return if options[:skip_launch]
|
224
|
+
cmd = WINDOWS ? "start" : "open"
|
225
|
+
system "#{cmd} #{specific_url || url}#{path}"
|
226
|
+
end
|
227
|
+
|
228
|
+
def kill!
|
229
|
+
pid = File.read(pid_file)
|
230
|
+
logger.warn "Sending #{kill_command} to #{pid.to_i}"
|
231
|
+
Process.kill(kill_command, pid.to_i)
|
232
|
+
rescue => e
|
233
|
+
logger.warn "pid not found at #{pid_file} : #{e}"
|
234
|
+
end
|
235
|
+
|
236
|
+
def status
|
237
|
+
if File.exist?(pid_file)
|
238
|
+
logger.info "'#{app_name}' running"
|
239
|
+
logger.info "PID #{File.read(pid_file)}"
|
240
|
+
logger.info "URL #{File.read(url_file)}" if File.exist?(url_file)
|
241
|
+
else
|
242
|
+
logger.info "'#{app_name}' not running!"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Loads a config file at config_path and evals it in the context of the @app.
|
247
|
+
def load_config_file(config_path)
|
248
|
+
abort "Can not find config file at #{config_path}" if !File.readable?(config_path)
|
249
|
+
config = File.read(config_path)
|
250
|
+
# trim off anything after __END__
|
251
|
+
config.sub!(/^__END__\n.*/, '')
|
252
|
+
@app.module_eval(config)
|
253
|
+
end
|
254
|
+
|
255
|
+
def self.logger=(logger)
|
256
|
+
@logger = logger
|
257
|
+
end
|
258
|
+
|
259
|
+
def self.logger
|
260
|
+
@logger ||= LOGGER if defined?(LOGGER)
|
261
|
+
if !@logger
|
262
|
+
@logger = Logger.new(STDOUT)
|
263
|
+
@logger.formatter = Proc.new {|s, t, n, msg| "[#{t}] #{msg}\n"}
|
264
|
+
@logger
|
265
|
+
end
|
266
|
+
@logger
|
267
|
+
end
|
268
|
+
|
269
|
+
def logger
|
270
|
+
self.class.logger
|
271
|
+
end
|
272
|
+
|
273
|
+
private
|
274
|
+
def setup_rack_handler
|
275
|
+
# First try to set Rack handler via a special hook we honor
|
276
|
+
@rack_handler = if @app.respond_to?(:detect_rack_handler)
|
277
|
+
@app.detect_rack_handler
|
278
|
+
|
279
|
+
# If they aren't using our hook, try to use their @app.server settings
|
280
|
+
elsif @app.respond_to?(:server) and @app.server
|
281
|
+
# If :server isn't set, it returns an array of possibilities,
|
282
|
+
# sorted from most to least preferable.
|
283
|
+
if @app.server.is_a?(Array)
|
284
|
+
handler = nil
|
285
|
+
@app.server.each do |server|
|
286
|
+
begin
|
287
|
+
handler = Rack::Handler.get(server)
|
288
|
+
break
|
289
|
+
rescue LoadError, NameError
|
290
|
+
next
|
291
|
+
end
|
292
|
+
end
|
293
|
+
raise 'No available Rack handler (e.g. WEBrick, Thin, Puma, etc.) was found.' if handler.nil?
|
294
|
+
|
295
|
+
handler
|
296
|
+
|
297
|
+
# :server might be set explicitly to a single option like "mongrel"
|
298
|
+
else
|
299
|
+
Rack::Handler.get(@app.server)
|
300
|
+
end
|
301
|
+
|
302
|
+
# If all else fails, we'll use Thin
|
303
|
+
else
|
304
|
+
JRUBY ? Rack::Handler::WEBrick : Rack::Handler::Thin
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def load_options(runtime_args)
|
309
|
+
@args = option_parser.parse!(runtime_args)
|
310
|
+
options.merge!(option_parser.options)
|
311
|
+
args
|
312
|
+
rescue OptionParser::MissingArgument => e
|
313
|
+
logger.warn "#{e}, run -h for options"
|
314
|
+
exit
|
315
|
+
end
|
316
|
+
|
317
|
+
def option_parser
|
318
|
+
@option_parser ||= Parser.new(app_name)
|
319
|
+
end
|
320
|
+
|
321
|
+
class Parser < OptionParser
|
322
|
+
attr_reader :command, :options
|
323
|
+
|
324
|
+
def initialize(app_name)
|
325
|
+
super("", 24, ' ')
|
326
|
+
self.banner = "Usage: #{app_name} [options]"
|
327
|
+
|
328
|
+
@options = {}
|
329
|
+
basename = app_name.gsub(/\W+/, "_")
|
330
|
+
on('-K', "--kill", "kill the running process and exit") { @command = :kill }
|
331
|
+
on('-S', "--status", "display the current running PID and URL then quit") { @command = :status }
|
332
|
+
string_option("-s", "--server SERVER", "serve using SERVER (thin/mongrel/webrick)", :rack_handler)
|
333
|
+
string_option("-o", "--host HOST", "listen on HOST (default: #{HOST})", :host)
|
334
|
+
string_option("-p", "--port PORT", "use PORT (default: #{PORT})", :port)
|
335
|
+
on("-x", "--no-proxy", "ignore env proxy settings (e.g. http_proxy)") { opts[:no_proxy] = true }
|
336
|
+
boolean_option("-F", "--foreground", "don't daemonize, run in the foreground", :foreground)
|
337
|
+
boolean_option("-L", "--no-launch", "don't launch the browser", :skip_launch)
|
338
|
+
boolean_option('-d', "--debug", "raise the log level to :debug (default: :info)", :debug)
|
339
|
+
string_option("--app-dir APP_DIR", "set the app dir where files are stored (default: ~/#{basename}/)", :app_dir)
|
340
|
+
string_option("-P", "--pid-file PID_FILE", "set the path to the pid file (default: app_dir/#{basename}.pid)", :pid_file)
|
341
|
+
string_option("--log-file LOG_FILE", "set the path to the log file (default: app_dir/#{basename}.log)", :log_file)
|
342
|
+
string_option("--url-file URL_FILE", "set the path to the URL file (default: app_dir/#{basename}.url)", :url_file)
|
343
|
+
string_option('-N NAMESPACE', "--namespace NAMESPACE", "set the Redis namespace", :redis_namespace)
|
344
|
+
string_option('-r redis-connection', "--redis redis-connection", "set the Redis connection string", :redis_conf)
|
345
|
+
string_option('-a url-prefix', "--append url-prefix", "set reverse_proxy friendly prefix to links", :url_prefix)
|
346
|
+
separator ""
|
347
|
+
separator "Common options:"
|
348
|
+
on_tail("-h", "--help", "Show this message") { @command = :help }
|
349
|
+
on_tail("--version", "Show version") { @command = :version }
|
350
|
+
end
|
351
|
+
|
352
|
+
def boolean_option(*argv)
|
353
|
+
k = argv.pop; on(*argv) { options[k] = true }
|
354
|
+
end
|
355
|
+
|
356
|
+
def string_option(*argv)
|
357
|
+
k = argv.pop; on(*argv) { |value| options[k] = value }
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def kill_commands
|
362
|
+
WINDOWS ? [1] : [:INT, :TERM]
|
363
|
+
end
|
364
|
+
|
365
|
+
def kill_command
|
366
|
+
kill_commands[0]
|
367
|
+
end
|
368
|
+
|
369
|
+
def delete_pid!
|
370
|
+
File.delete(pid_file) if File.exist?(pid_file)
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
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,20 @@ module Resque
|
|
175
177
|
self.reconnect if ENV['BACKGROUND']
|
176
178
|
end
|
177
179
|
|
180
|
+
WILDCARDS = ['*', '?', '{', '}', '[', ']'].freeze
|
181
|
+
|
178
182
|
def queues=(queues)
|
179
|
-
queues =
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
183
|
+
queues = (ENV["QUEUES"] || ENV['QUEUE']).to_s.split(',') if queues.empty?
|
184
|
+
queues = queues.map { |queue| queue.to_s.strip }
|
185
|
+
|
186
|
+
@skip_queues, @queues = queues.partition { |queue| queue.start_with?('!') }
|
187
|
+
@skip_queues.map! { |queue| queue[1..-1] }
|
188
|
+
|
189
|
+
# The behavior of `queues` is dependent on the value of `@has_dynamic_queues: if it's true, the method returns the result of filtering @queues with `glob_match`
|
190
|
+
# if it's false, the method returns @queues directly. Since `glob_match` will cause skipped queues to be filtered out, we want to make sure it's called if we have @skip_queues.any?
|
191
|
+
@has_dynamic_queues =
|
192
|
+
@skip_queues.any? || WILDCARDS.any? { |char| @queues.join.include?(char) }
|
193
|
+
|
184
194
|
validate_queues
|
185
195
|
end
|
186
196
|
|
@@ -198,13 +208,18 @@ module Resque
|
|
198
208
|
# A splat ("*") means you want every queue (in alpha order) - this
|
199
209
|
# can be useful for dynamically adding new queues.
|
200
210
|
def queues
|
201
|
-
|
202
|
-
|
211
|
+
if @has_dynamic_queues
|
212
|
+
current_queues = Resque.queues
|
213
|
+
@queues.map { |queue| glob_match(current_queues, queue) }.flatten.uniq
|
214
|
+
else
|
215
|
+
@queues
|
216
|
+
end
|
203
217
|
end
|
204
218
|
|
205
|
-
def glob_match(pattern)
|
206
|
-
|
207
|
-
File.fnmatch?(pattern, queue)
|
219
|
+
def glob_match(list, pattern)
|
220
|
+
list.select do |queue|
|
221
|
+
File.fnmatch?(pattern, queue) &&
|
222
|
+
@skip_queues.none? { |skip_pattern| File.fnmatch?(skip_pattern, queue) }
|
208
223
|
end.sort
|
209
224
|
end
|
210
225
|
|
@@ -232,6 +247,7 @@ module Resque
|
|
232
247
|
break if shutdown?
|
233
248
|
|
234
249
|
unless work_one_job(&block)
|
250
|
+
state_change
|
235
251
|
break if interval.zero?
|
236
252
|
log_with_severity :debug, "Sleeping for #{interval} seconds"
|
237
253
|
procline paused? ? "Paused" : "Waiting for #{queues.join(',')}"
|
@@ -240,10 +256,12 @@ module Resque
|
|
240
256
|
end
|
241
257
|
|
242
258
|
unregister_worker
|
259
|
+
run_hook :worker_exit
|
243
260
|
rescue Exception => exception
|
244
261
|
return if exception.class == SystemExit && !@child && run_at_exit_hooks
|
245
262
|
log_with_severity :error, "Failed to start worker : #{exception.inspect}"
|
246
263
|
unregister_worker(exception)
|
264
|
+
run_hook :worker_exit
|
247
265
|
end
|
248
266
|
|
249
267
|
def work_one_job(job = nil, &block)
|
@@ -482,20 +500,22 @@ module Resque
|
|
482
500
|
# Returns a list of workers that have sent a heartbeat in the past, but which
|
483
501
|
# already expired (does NOT include workers that have never sent a heartbeat at all).
|
484
502
|
def self.all_workers_with_expired_heartbeats
|
485
|
-
|
503
|
+
# Use `Worker.all_heartbeats` instead of `Worker.all`
|
504
|
+
# to prune workers which haven't been registered but have set a heartbeat.
|
505
|
+
# https://github.com/resque/resque/pull/1751
|
486
506
|
heartbeats = Worker.all_heartbeats
|
487
507
|
now = data_store.server_time
|
488
508
|
|
489
|
-
|
490
|
-
id = worker.to_s
|
491
|
-
heartbeat = heartbeats[id]
|
492
|
-
|
509
|
+
heartbeats.select do |id, heartbeat|
|
493
510
|
if heartbeat
|
494
511
|
seconds_since_heartbeat = (now - Time.parse(heartbeat)).to_i
|
495
512
|
seconds_since_heartbeat > Resque.prune_interval
|
496
513
|
else
|
497
514
|
false
|
498
515
|
end
|
516
|
+
end.each_key.map do |id|
|
517
|
+
# skip_exists must be true to include not registered workers
|
518
|
+
find(id, :skip_exists => true)
|
499
519
|
end
|
500
520
|
end
|
501
521
|
|
@@ -559,7 +579,7 @@ module Resque
|
|
559
579
|
|
560
580
|
# are we paused?
|
561
581
|
def paused?
|
562
|
-
@paused
|
582
|
+
@paused || redis.get('pause-all-workers').to_s.strip.downcase == 'true'
|
563
583
|
end
|
564
584
|
|
565
585
|
# Stop processing jobs after the current one has completed (if we're
|
@@ -588,23 +608,27 @@ module Resque
|
|
588
608
|
# By checking the current Redis state against the actual
|
589
609
|
# environment, we can determine if Redis is old and clean it up a bit.
|
590
610
|
def prune_dead_workers
|
591
|
-
|
611
|
+
return unless data_store.acquire_pruning_dead_worker_lock(self, Resque.heartbeat_interval)
|
592
612
|
|
593
|
-
|
594
|
-
known_workers = worker_pids
|
595
|
-
all_workers_with_expired_heartbeats = Worker.all_workers_with_expired_heartbeats
|
596
|
-
end
|
613
|
+
all_workers = Worker.all
|
597
614
|
|
598
|
-
|
615
|
+
known_workers = worker_pids
|
616
|
+
all_workers_with_expired_heartbeats = Worker.all_workers_with_expired_heartbeats
|
617
|
+
all_workers_with_expired_heartbeats.each do |worker|
|
599
618
|
# If the worker hasn't sent a heartbeat, remove it from the registry.
|
600
619
|
#
|
601
620
|
# If the worker hasn't ever sent a heartbeat, we won't remove it since
|
602
621
|
# the first heartbeat is sent before the worker is registred it means
|
603
622
|
# that this is a worker that doesn't support heartbeats, e.g., another
|
604
623
|
# client library or an older version of Resque. We won't touch these.
|
624
|
+
log_with_severity :info, "Pruning dead worker: #{worker}"
|
625
|
+
|
626
|
+
job_class = worker.job(false)['payload']['class'] rescue nil
|
627
|
+
worker.unregister_worker(PruneDeadWorkerDirtyExit.new(worker.to_s, job_class))
|
628
|
+
end
|
629
|
+
|
630
|
+
all_workers.each do |worker|
|
605
631
|
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
632
|
next
|
609
633
|
end
|
610
634
|
|
@@ -635,7 +659,8 @@ module Resque
|
|
635
659
|
|
636
660
|
# Runs a named hook, passing along any arguments.
|
637
661
|
def run_hook(name, *args)
|
638
|
-
|
662
|
+
hooks = Resque.send(name)
|
663
|
+
return if hooks.empty?
|
639
664
|
return if name == :before_first_fork && @before_first_fork_hook_ran
|
640
665
|
msg = "Running #{name} hooks"
|
641
666
|
msg << " with #{args.inspect}" if args.any?
|
@@ -672,9 +697,9 @@ module Resque
|
|
672
697
|
|
673
698
|
kill_background_threads
|
674
699
|
|
675
|
-
data_store.unregister_worker(self) do
|
676
|
-
Stat.clear("processed:#{self}")
|
677
|
-
Stat.clear("failed:#{self}")
|
700
|
+
data_store.unregister_worker(self) do |**opts|
|
701
|
+
Stat.clear("processed:#{self}", **opts)
|
702
|
+
Stat.clear("failed:#{self}", **opts)
|
678
703
|
end
|
679
704
|
rescue Exception => exception_while_unregistering
|
680
705
|
message = exception_while_unregistering.message
|
@@ -695,13 +720,22 @@ module Resque
|
|
695
720
|
:run_at => Time.now.utc.iso8601,
|
696
721
|
:payload => job.payload
|
697
722
|
data_store.set_worker_payload(self,data)
|
723
|
+
state_change
|
698
724
|
end
|
699
725
|
|
700
726
|
# Called when we are done working - clears our `working_on` state
|
701
727
|
# and tells Redis we processed a job.
|
702
728
|
def done_working
|
703
|
-
data_store.worker_done_working(self) do
|
704
|
-
processed!
|
729
|
+
data_store.worker_done_working(self) do |**opts|
|
730
|
+
processed!(**opts)
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
def state_change
|
735
|
+
current_state = state
|
736
|
+
if current_state != @last_state
|
737
|
+
run_hook :queue_empty if current_state == :idle
|
738
|
+
@last_state = current_state
|
705
739
|
end
|
706
740
|
end
|
707
741
|
|
@@ -711,9 +745,9 @@ module Resque
|
|
711
745
|
end
|
712
746
|
|
713
747
|
# Tell Redis we've processed a job.
|
714
|
-
def processed!
|
715
|
-
Stat
|
716
|
-
Stat
|
748
|
+
def processed!(**opts)
|
749
|
+
Stat.incr("processed", 1, **opts)
|
750
|
+
Stat.incr("processed:#{self}", 1, **opts)
|
717
751
|
end
|
718
752
|
|
719
753
|
# How many failed jobs has this worker seen? Returns an int.
|
@@ -808,7 +842,7 @@ module Resque
|
|
808
842
|
# machine. Useful when pruning dead workers on startup.
|
809
843
|
def windows_worker_pids
|
810
844
|
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
|
845
|
+
tasklist_output.split($/).select { |line| line =~ /^PID:/ }.collect { |line| line.gsub(/PID:\s+/, '') }
|
812
846
|
end
|
813
847
|
|
814
848
|
# Find Resque worker pids on Linux and OS X.
|
@@ -827,7 +861,7 @@ module Resque
|
|
827
861
|
`ps -A -o pid,comm | grep "[r]uby" | grep -v "resque-web"`.split("\n").map do |line|
|
828
862
|
real_pid = line.split(' ')[0]
|
829
863
|
pargs_command = `pargs -a #{real_pid} 2>/dev/null | grep [r]esque | grep -v "resque-web"`
|
830
|
-
if pargs_command.split(':')[1] == " resque-#{Resque::
|
864
|
+
if pargs_command.split(':')[1] == " resque-#{Resque::VERSION}"
|
831
865
|
real_pid
|
832
866
|
end
|
833
867
|
end.compact
|
@@ -837,7 +871,7 @@ module Resque
|
|
837
871
|
# Procline is always in the format of:
|
838
872
|
# RESQUE_PROCLINE_PREFIXresque-VERSION: STRING
|
839
873
|
def procline(string)
|
840
|
-
$0 = "#{ENV['RESQUE_PROCLINE_PREFIX']}resque-#{Resque::
|
874
|
+
$0 = "#{ENV['RESQUE_PROCLINE_PREFIX']}resque-#{Resque::VERSION}: #{string}"
|
841
875
|
log_with_severity :debug, $0
|
842
876
|
end
|
843
877
|
|
@@ -850,13 +884,7 @@ module Resque
|
|
850
884
|
end
|
851
885
|
|
852
886
|
|
853
|
-
|
854
|
-
@verbose
|
855
|
-
end
|
856
|
-
|
857
|
-
def very_verbose
|
858
|
-
@very_verbose
|
859
|
-
end
|
887
|
+
attr_reader :verbose, :very_verbose
|
860
888
|
|
861
889
|
def verbose=(value);
|
862
890
|
if value && !very_verbose
|
@@ -909,7 +937,7 @@ module Resque
|
|
909
937
|
nil
|
910
938
|
end
|
911
939
|
|
912
|
-
job.fail(DirtyExit.new("Child process received unhandled signal #{
|
940
|
+
job.fail(DirtyExit.new("Child process received unhandled signal #{$?}", $?)) if $?.signaled?
|
913
941
|
@child = nil
|
914
942
|
end
|
915
943
|
|