resque 2.0.0 → 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 +4 -4
- data/HISTORY.md +102 -0
- data/README.markdown +419 -515
- data/bin/resque-web +10 -26
- data/lib/resque/data_store.rb +32 -29
- data/lib/resque/failure/multiple.rb +6 -2
- data/lib/resque/failure/redis.rb +1 -1
- data/lib/resque/failure.rb +6 -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/job_class.erb +3 -1
- data/lib/resque/server/views/key_string.erb +1 -1
- data/lib/resque/server/views/layout.erb +4 -3
- data/lib/resque/server/views/next_more.erb +14 -14
- data/lib/resque/server/views/queues.erb +3 -3
- data/lib/resque/server/views/stats.erb +4 -4
- data/lib/resque/server/views/working.erb +1 -1
- data/lib/resque/server.rb +7 -113
- data/lib/resque/server_helper.rb +185 -0
- data/lib/resque/stat.rb +4 -4
- data/lib/resque/tasks.rb +2 -2
- data/lib/resque/version.rb +1 -1
- data/lib/resque/web_runner.rb +374 -0
- data/lib/resque/worker.rb +56 -31
- data/lib/resque.rb +39 -10
- data/lib/tasks/redis.rake +10 -10
- metadata +41 -23
- 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
@@ -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
|
@@ -148,6 +148,8 @@ module Resque
|
|
148
148
|
@heartbeat_thread = nil
|
149
149
|
@heartbeat_thread_signal = nil
|
150
150
|
|
151
|
+
@last_state = :idle
|
152
|
+
|
151
153
|
verbose_value = ENV['LOGGING'] || ENV['VERBOSE']
|
152
154
|
self.verbose = verbose_value if verbose_value
|
153
155
|
self.very_verbose = ENV['VVERBOSE'] if ENV['VVERBOSE']
|
@@ -178,9 +180,17 @@ module Resque
|
|
178
180
|
WILDCARDS = ['*', '?', '{', '}', '[', ']'].freeze
|
179
181
|
|
180
182
|
def queues=(queues)
|
181
|
-
queues =
|
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
|
|
@@ -208,7 +218,8 @@ module Resque
|
|
208
218
|
|
209
219
|
def glob_match(list, pattern)
|
210
220
|
list.select do |queue|
|
211
|
-
File.fnmatch?(pattern, queue)
|
221
|
+
File.fnmatch?(pattern, queue) &&
|
222
|
+
@skip_queues.none? { |skip_pattern| File.fnmatch?(skip_pattern, queue) }
|
212
223
|
end.sort
|
213
224
|
end
|
214
225
|
|
@@ -236,6 +247,7 @@ module Resque
|
|
236
247
|
break if shutdown?
|
237
248
|
|
238
249
|
unless work_one_job(&block)
|
250
|
+
state_change
|
239
251
|
break if interval.zero?
|
240
252
|
log_with_severity :debug, "Sleeping for #{interval} seconds"
|
241
253
|
procline paused? ? "Paused" : "Waiting for #{queues.join(',')}"
|
@@ -244,10 +256,12 @@ module Resque
|
|
244
256
|
end
|
245
257
|
|
246
258
|
unregister_worker
|
259
|
+
run_hook :worker_exit
|
247
260
|
rescue Exception => exception
|
248
261
|
return if exception.class == SystemExit && !@child && run_at_exit_hooks
|
249
262
|
log_with_severity :error, "Failed to start worker : #{exception.inspect}"
|
250
263
|
unregister_worker(exception)
|
264
|
+
run_hook :worker_exit
|
251
265
|
end
|
252
266
|
|
253
267
|
def work_one_job(job = nil, &block)
|
@@ -486,20 +500,22 @@ module Resque
|
|
486
500
|
# Returns a list of workers that have sent a heartbeat in the past, but which
|
487
501
|
# already expired (does NOT include workers that have never sent a heartbeat at all).
|
488
502
|
def self.all_workers_with_expired_heartbeats
|
489
|
-
|
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
|
490
506
|
heartbeats = Worker.all_heartbeats
|
491
507
|
now = data_store.server_time
|
492
508
|
|
493
|
-
|
494
|
-
id = worker.to_s
|
495
|
-
heartbeat = heartbeats[id]
|
496
|
-
|
509
|
+
heartbeats.select do |id, heartbeat|
|
497
510
|
if heartbeat
|
498
511
|
seconds_since_heartbeat = (now - Time.parse(heartbeat)).to_i
|
499
512
|
seconds_since_heartbeat > Resque.prune_interval
|
500
513
|
else
|
501
514
|
false
|
502
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)
|
503
519
|
end
|
504
520
|
end
|
505
521
|
|
@@ -563,7 +579,7 @@ module Resque
|
|
563
579
|
|
564
580
|
# are we paused?
|
565
581
|
def paused?
|
566
|
-
@paused
|
582
|
+
@paused || redis.get('pause-all-workers').to_s.strip.downcase == 'true'
|
567
583
|
end
|
568
584
|
|
569
585
|
# Stop processing jobs after the current one has completed (if we're
|
@@ -596,23 +612,23 @@ module Resque
|
|
596
612
|
|
597
613
|
all_workers = Worker.all
|
598
614
|
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
end
|
603
|
-
|
604
|
-
all_workers.each do |worker|
|
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|
|
605
618
|
# If the worker hasn't sent a heartbeat, remove it from the registry.
|
606
619
|
#
|
607
620
|
# If the worker hasn't ever sent a heartbeat, we won't remove it since
|
608
621
|
# the first heartbeat is sent before the worker is registred it means
|
609
622
|
# that this is a worker that doesn't support heartbeats, e.g., another
|
610
623
|
# client library or an older version of Resque. We won't touch these.
|
611
|
-
|
612
|
-
|
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
|
613
629
|
|
614
|
-
|
615
|
-
|
630
|
+
all_workers.each do |worker|
|
631
|
+
if all_workers_with_expired_heartbeats.include?(worker)
|
616
632
|
next
|
617
633
|
end
|
618
634
|
|
@@ -681,9 +697,9 @@ module Resque
|
|
681
697
|
|
682
698
|
kill_background_threads
|
683
699
|
|
684
|
-
data_store.unregister_worker(self) do
|
685
|
-
Stat.clear("processed:#{self}")
|
686
|
-
Stat.clear("failed:#{self}")
|
700
|
+
data_store.unregister_worker(self) do |**opts|
|
701
|
+
Stat.clear("processed:#{self}", **opts)
|
702
|
+
Stat.clear("failed:#{self}", **opts)
|
687
703
|
end
|
688
704
|
rescue Exception => exception_while_unregistering
|
689
705
|
message = exception_while_unregistering.message
|
@@ -704,13 +720,22 @@ module Resque
|
|
704
720
|
:run_at => Time.now.utc.iso8601,
|
705
721
|
:payload => job.payload
|
706
722
|
data_store.set_worker_payload(self,data)
|
723
|
+
state_change
|
707
724
|
end
|
708
725
|
|
709
726
|
# Called when we are done working - clears our `working_on` state
|
710
727
|
# and tells Redis we processed a job.
|
711
728
|
def done_working
|
712
|
-
data_store.worker_done_working(self) do
|
713
|
-
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
|
714
739
|
end
|
715
740
|
end
|
716
741
|
|
@@ -720,9 +745,9 @@ module Resque
|
|
720
745
|
end
|
721
746
|
|
722
747
|
# Tell Redis we've processed a job.
|
723
|
-
def processed!
|
724
|
-
Stat
|
725
|
-
Stat
|
748
|
+
def processed!(**opts)
|
749
|
+
Stat.incr("processed", 1, **opts)
|
750
|
+
Stat.incr("processed:#{self}", 1, **opts)
|
726
751
|
end
|
727
752
|
|
728
753
|
# How many failed jobs has this worker seen? Returns an int.
|
@@ -836,7 +861,7 @@ module Resque
|
|
836
861
|
`ps -A -o pid,comm | grep "[r]uby" | grep -v "resque-web"`.split("\n").map do |line|
|
837
862
|
real_pid = line.split(' ')[0]
|
838
863
|
pargs_command = `pargs -a #{real_pid} 2>/dev/null | grep [r]esque | grep -v "resque-web"`
|
839
|
-
if pargs_command.split(':')[1] == " resque-#{Resque::
|
864
|
+
if pargs_command.split(':')[1] == " resque-#{Resque::VERSION}"
|
840
865
|
real_pid
|
841
866
|
end
|
842
867
|
end.compact
|
@@ -846,7 +871,7 @@ module Resque
|
|
846
871
|
# Procline is always in the format of:
|
847
872
|
# RESQUE_PROCLINE_PREFIXresque-VERSION: STRING
|
848
873
|
def procline(string)
|
849
|
-
$0 = "#{ENV['RESQUE_PROCLINE_PREFIX']}resque-#{Resque::
|
874
|
+
$0 = "#{ENV['RESQUE_PROCLINE_PREFIX']}resque-#{Resque::VERSION}: #{string}"
|
850
875
|
log_with_severity :debug, $0
|
851
876
|
end
|
852
877
|
|
data/lib/resque.rb
CHANGED
@@ -23,6 +23,8 @@ require 'resque/thread_signal'
|
|
23
23
|
|
24
24
|
require 'resque/vendor/utf8_util'
|
25
25
|
|
26
|
+
require 'resque/railtie' if defined?(Rails::Railtie)
|
27
|
+
|
26
28
|
module Resque
|
27
29
|
include Helpers
|
28
30
|
extend self
|
@@ -113,12 +115,11 @@ module Resque
|
|
113
115
|
case server
|
114
116
|
when String
|
115
117
|
if server =~ /rediss?\:\/\//
|
116
|
-
redis = Redis.new(:url => server
|
118
|
+
redis = Redis.new(:url => server)
|
117
119
|
else
|
118
120
|
server, namespace = server.split('/', 2)
|
119
121
|
host, port, db = server.split(':')
|
120
|
-
redis = Redis.new(:host => host, :port => port,
|
121
|
-
:thread_safe => true, :db => db)
|
122
|
+
redis = Redis.new(:host => host, :port => port, :db => db)
|
122
123
|
end
|
123
124
|
namespace ||= :resque
|
124
125
|
|
@@ -287,6 +288,34 @@ module Resque
|
|
287
288
|
register_hook(:after_pause, block)
|
288
289
|
end
|
289
290
|
|
291
|
+
# The `queue_empty` hook will be run in the **parent** process when
|
292
|
+
# the worker finds no more jobs in the queue and becomes idle.
|
293
|
+
#
|
294
|
+
# Call with a block to register a hook.
|
295
|
+
# Call with no arguments to return all registered hooks.
|
296
|
+
def queue_empty(&block)
|
297
|
+
block ? register_hook(:queue_empty, block) : hooks(:queue_empty)
|
298
|
+
end
|
299
|
+
|
300
|
+
# Register a queue_empty proc.
|
301
|
+
def queue_empty=(block)
|
302
|
+
register_hook(:queue_empty, block)
|
303
|
+
end
|
304
|
+
|
305
|
+
# The `worker_exit` hook will be run in the **parent** process
|
306
|
+
# after the worker has existed (via SIGQUIT, SIGTERM, SIGINT, etc.).
|
307
|
+
#
|
308
|
+
# Call with a block to register a hook.
|
309
|
+
# Call with no arguments to return all registered hooks.
|
310
|
+
def worker_exit(&block)
|
311
|
+
block ? register_hook(:worker_exit, block) : hooks(:worker_exit)
|
312
|
+
end
|
313
|
+
|
314
|
+
# Register a worker_exit proc.
|
315
|
+
def worker_exit=(block)
|
316
|
+
register_hook(:worker_exit, block)
|
317
|
+
end
|
318
|
+
|
290
319
|
def to_s
|
291
320
|
"Resque Client connected to #{redis_id}"
|
292
321
|
end
|
@@ -334,8 +363,8 @@ module Resque
|
|
334
363
|
data_store.queue_size(queue)
|
335
364
|
end
|
336
365
|
|
337
|
-
# Returns an array of items currently queued
|
338
|
-
# a string.
|
366
|
+
# Returns an array of items currently queued, or the item itself
|
367
|
+
# if count = 1. Queue name should be a string.
|
339
368
|
#
|
340
369
|
# start and count should be integer and can be used for pagination.
|
341
370
|
# start is the item to begin, count is how many items to return.
|
@@ -554,9 +583,9 @@ module Resque
|
|
554
583
|
def queue_sizes
|
555
584
|
queue_names = queues
|
556
585
|
|
557
|
-
sizes = redis.pipelined do
|
586
|
+
sizes = redis.pipelined do |piped|
|
558
587
|
queue_names.each do |name|
|
559
|
-
|
588
|
+
piped.llen("queue:#{name}")
|
560
589
|
end
|
561
590
|
end
|
562
591
|
|
@@ -567,11 +596,11 @@ module Resque
|
|
567
596
|
def sample_queues(sample_size = 1000)
|
568
597
|
queue_names = queues
|
569
598
|
|
570
|
-
samples = redis.pipelined do
|
599
|
+
samples = redis.pipelined do |piped|
|
571
600
|
queue_names.each do |name|
|
572
601
|
key = "queue:#{name}"
|
573
|
-
|
574
|
-
|
602
|
+
piped.llen(key)
|
603
|
+
piped.lrange(key, 0, sample_size - 1)
|
575
604
|
end
|
576
605
|
end
|
577
606
|
|