resque_sqs 1.25.2
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 +7 -0
- data/HISTORY.md +467 -0
- data/LICENSE +20 -0
- data/README.markdown +866 -0
- data/Rakefile +70 -0
- data/bin/resque-sqs +81 -0
- data/bin/resque-sqs-web +27 -0
- data/lib/resque_sqs/errors.rb +13 -0
- data/lib/resque_sqs/failure/airbrake.rb +33 -0
- data/lib/resque_sqs/failure/base.rb +73 -0
- data/lib/resque_sqs/failure/multiple.rb +59 -0
- data/lib/resque_sqs/failure/redis.rb +108 -0
- data/lib/resque_sqs/failure/redis_multi_queue.rb +89 -0
- data/lib/resque_sqs/failure.rb +113 -0
- data/lib/resque_sqs/helpers.rb +107 -0
- data/lib/resque_sqs/job.rb +346 -0
- data/lib/resque_sqs/log_formatters/quiet_formatter.rb +7 -0
- data/lib/resque_sqs/log_formatters/verbose_formatter.rb +7 -0
- data/lib/resque_sqs/log_formatters/very_verbose_formatter.rb +8 -0
- data/lib/resque_sqs/logging.rb +18 -0
- data/lib/resque_sqs/plugin.rb +66 -0
- data/lib/resque_sqs/server/helpers.rb +52 -0
- data/lib/resque_sqs/server/public/favicon.ico +0 -0
- data/lib/resque_sqs/server/public/idle.png +0 -0
- data/lib/resque_sqs/server/public/jquery-1.3.2.min.js +19 -0
- data/lib/resque_sqs/server/public/jquery.relatize_date.js +95 -0
- data/lib/resque_sqs/server/public/poll.png +0 -0
- data/lib/resque_sqs/server/public/ranger.js +78 -0
- data/lib/resque_sqs/server/public/reset.css +44 -0
- data/lib/resque_sqs/server/public/style.css +91 -0
- data/lib/resque_sqs/server/public/working.png +0 -0
- data/lib/resque_sqs/server/test_helper.rb +19 -0
- data/lib/resque_sqs/server/views/error.erb +1 -0
- data/lib/resque_sqs/server/views/failed.erb +29 -0
- data/lib/resque_sqs/server/views/failed_job.erb +50 -0
- data/lib/resque_sqs/server/views/failed_queues_overview.erb +24 -0
- data/lib/resque_sqs/server/views/key_sets.erb +19 -0
- data/lib/resque_sqs/server/views/key_string.erb +11 -0
- data/lib/resque_sqs/server/views/layout.erb +44 -0
- data/lib/resque_sqs/server/views/next_more.erb +22 -0
- data/lib/resque_sqs/server/views/overview.erb +4 -0
- data/lib/resque_sqs/server/views/queues.erb +58 -0
- data/lib/resque_sqs/server/views/stats.erb +62 -0
- data/lib/resque_sqs/server/views/workers.erb +109 -0
- data/lib/resque_sqs/server/views/working.erb +72 -0
- data/lib/resque_sqs/server.rb +271 -0
- data/lib/resque_sqs/stat.rb +57 -0
- data/lib/resque_sqs/tasks.rb +83 -0
- data/lib/resque_sqs/vendor/utf8_util/utf8_util_18.rb +91 -0
- data/lib/resque_sqs/vendor/utf8_util/utf8_util_19.rb +5 -0
- data/lib/resque_sqs/vendor/utf8_util.rb +20 -0
- data/lib/resque_sqs/version.rb +3 -0
- data/lib/resque_sqs/worker.rb +779 -0
- data/lib/resque_sqs.rb +479 -0
- data/lib/tasks/redis_sqs.rake +161 -0
- data/lib/tasks/resque_sqs.rake +2 -0
- data/test/airbrake_test.rb +27 -0
- data/test/failure_base_test.rb +15 -0
- data/test/job_hooks_test.rb +465 -0
- data/test/job_plugins_test.rb +230 -0
- data/test/logging_test.rb +24 -0
- data/test/plugin_test.rb +116 -0
- data/test/redis-test-cluster.conf +115 -0
- data/test/redis-test.conf +115 -0
- data/test/resque-web_test.rb +59 -0
- data/test/resque_failure_redis_test.rb +19 -0
- data/test/resque_hook_test.rb +165 -0
- data/test/resque_test.rb +278 -0
- data/test/stdout +42 -0
- data/test/test_helper.rb +228 -0
- data/test/worker_test.rb +1080 -0
- metadata +202 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Setup
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
load 'lib/tasks/redis_sqs.rake'
|
|
6
|
+
|
|
7
|
+
$LOAD_PATH.unshift 'lib'
|
|
8
|
+
require 'resque_sqs/tasks'
|
|
9
|
+
|
|
10
|
+
def command?(command)
|
|
11
|
+
system("type #{command} > /dev/null 2>&1")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
# Tests
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
require 'rake/testtask'
|
|
20
|
+
|
|
21
|
+
task :default => :test
|
|
22
|
+
|
|
23
|
+
Rake::TestTask.new do |test|
|
|
24
|
+
test.verbose = true
|
|
25
|
+
test.libs << "test"
|
|
26
|
+
test.libs << "lib"
|
|
27
|
+
test.test_files = FileList['test/**/*_test.rb']
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if command? :kicker
|
|
31
|
+
desc "Launch Kicker (like autotest)"
|
|
32
|
+
task :kicker do
|
|
33
|
+
puts "Kicking... (ctrl+c to cancel)"
|
|
34
|
+
exec "kicker -e rake test lib examples"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
#
|
|
40
|
+
# Install
|
|
41
|
+
#
|
|
42
|
+
|
|
43
|
+
task :install => [ 'redis_sqs:install', 'dtach_sqs:install' ]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
#
|
|
47
|
+
# Documentation
|
|
48
|
+
#
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
require 'sdoc_helpers'
|
|
52
|
+
rescue LoadError
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
#
|
|
57
|
+
# Publishing
|
|
58
|
+
#
|
|
59
|
+
|
|
60
|
+
desc "Push a new version to Gemcutter"
|
|
61
|
+
task :sqs_publish do
|
|
62
|
+
require 'resque_sqs/version'
|
|
63
|
+
|
|
64
|
+
sh "gem build resque_sqs.gemspec"
|
|
65
|
+
sh "gem push resque_sqs-#{ResqueSqs::Version}.gem"
|
|
66
|
+
sh "git tag v#{ResqueSqs::Version}"
|
|
67
|
+
sh "git push origin v#{ResqueSqs::Version}"
|
|
68
|
+
sh "git push origin master"
|
|
69
|
+
sh "git clean -fd"
|
|
70
|
+
end
|
data/bin/resque-sqs
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
|
4
|
+
begin
|
|
5
|
+
require 'redis-namespace'
|
|
6
|
+
rescue LoadError
|
|
7
|
+
require 'rubygems'
|
|
8
|
+
require 'redis-namespace'
|
|
9
|
+
end
|
|
10
|
+
require 'resque_sqs'
|
|
11
|
+
require 'optparse'
|
|
12
|
+
|
|
13
|
+
parser = OptionParser.new do |opts|
|
|
14
|
+
opts.banner = "Usage: resque [options] COMMAND"
|
|
15
|
+
|
|
16
|
+
opts.separator ""
|
|
17
|
+
opts.separator "Options:"
|
|
18
|
+
|
|
19
|
+
opts.on("-r", "--redis [HOST:PORT]", "Redis connection string") do |host|
|
|
20
|
+
ResqueSqs.redis = host
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
opts.on("-N", "--namespace [NAMESPACE]", "Redis namespace") do |namespace|
|
|
24
|
+
ResqueSqs.redis.namespace = namespace
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
opts.on("-h", "--help", "Show this message") do
|
|
28
|
+
puts opts
|
|
29
|
+
exit
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
opts.separator ""
|
|
33
|
+
opts.separator "Commands:"
|
|
34
|
+
opts.separator " remove WORKER Removes a worker"
|
|
35
|
+
opts.separator " kill WORKER Kills a worker"
|
|
36
|
+
opts.separator " list Lists known workers"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def kill(worker)
|
|
40
|
+
abort "** resque kill WORKER_ID" if worker.nil?
|
|
41
|
+
pid = worker.split(':')[1].to_i
|
|
42
|
+
|
|
43
|
+
begin
|
|
44
|
+
Process.kill("KILL", pid)
|
|
45
|
+
puts "** killed #{worker}"
|
|
46
|
+
rescue Errno::ESRCH
|
|
47
|
+
puts "** worker #{worker} not running"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
remove worker
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def remove(worker)
|
|
54
|
+
abort "** resque remove WORKER_ID" if worker.nil?
|
|
55
|
+
|
|
56
|
+
ResqueSqs.remove_worker(worker)
|
|
57
|
+
puts "** removed #{worker}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def list
|
|
61
|
+
if ResqueSqs.workers.any?
|
|
62
|
+
ResqueSqs.workers.each do |worker|
|
|
63
|
+
puts "#{worker} (#{worker.state})"
|
|
64
|
+
end
|
|
65
|
+
else
|
|
66
|
+
puts "None"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
parser.parse!
|
|
71
|
+
|
|
72
|
+
case ARGV[0]
|
|
73
|
+
when 'kill'
|
|
74
|
+
kill ARGV[1]
|
|
75
|
+
when 'remove'
|
|
76
|
+
remove ARGV[1]
|
|
77
|
+
when 'list'
|
|
78
|
+
list
|
|
79
|
+
else
|
|
80
|
+
puts parser.help
|
|
81
|
+
end
|
data/bin/resque-sqs-web
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
|
4
|
+
begin
|
|
5
|
+
require 'vegas'
|
|
6
|
+
rescue LoadError
|
|
7
|
+
require 'rubygems'
|
|
8
|
+
require 'vegas'
|
|
9
|
+
end
|
|
10
|
+
require 'resque_sqs/server'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
Vegas::Runner.new(ResqueSqs::Server, 'resque-web', {
|
|
14
|
+
:before_run => lambda {|v|
|
|
15
|
+
path = (ENV['RESQUECONFIG'] || v.args.first)
|
|
16
|
+
load path.to_s.strip if path
|
|
17
|
+
}
|
|
18
|
+
}) do |runner, opts, app|
|
|
19
|
+
opts.on('-N NAMESPACE', "--namespace NAMESPACE", "set the Redis namespace") {|namespace|
|
|
20
|
+
runner.logger.info "Using Redis namespace '#{namespace}'"
|
|
21
|
+
ResqueSqs.redis.namespace = namespace
|
|
22
|
+
}
|
|
23
|
+
opts.on('-r redis-connection', "--redis redis-connection", "set the Redis connection string") {|redis_conf|
|
|
24
|
+
runner.logger.info "Using Redis connection '#{redis_conf}'"
|
|
25
|
+
ResqueSqs.redis = redis_conf
|
|
26
|
+
}
|
|
27
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module ResqueSqs
|
|
2
|
+
# Raised whenever we need a queue but none is provided.
|
|
3
|
+
class NoQueueError < RuntimeError; end
|
|
4
|
+
|
|
5
|
+
# Raised when trying to create a job without a class
|
|
6
|
+
class NoClassError < RuntimeError; end
|
|
7
|
+
|
|
8
|
+
# Raised when a worker was killed while processing a job.
|
|
9
|
+
class DirtyExit < RuntimeError; end
|
|
10
|
+
|
|
11
|
+
# Raised when child process is TERM'd so job can rescue this to do shutdown work.
|
|
12
|
+
class TermException < SignalException; end
|
|
13
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'airbrake'
|
|
3
|
+
rescue LoadError
|
|
4
|
+
raise "Can't find 'airbrake' gem. Please add it to your Gemfile or install it."
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module ResqueSqs
|
|
8
|
+
module Failure
|
|
9
|
+
class Airbrake < Base
|
|
10
|
+
def self.configure(&block)
|
|
11
|
+
ResqueSqs.logger.warn "This actually sets global Airbrake configuration, " \
|
|
12
|
+
"which is probably not what you want. This will be gone in 2.0."
|
|
13
|
+
ResqueSqs::Failure.backend = self
|
|
14
|
+
::Airbrake.configure(&block)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.count(queue = nil, class_name = nil)
|
|
18
|
+
# We can't get the total # of errors from Airbrake so we fake it
|
|
19
|
+
# by asking Resque how many errors it has seen.
|
|
20
|
+
Stat[:failed]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def save
|
|
24
|
+
::Airbrake.notify_or_ignore(exception,
|
|
25
|
+
:parameters => {
|
|
26
|
+
:payload_class => payload['class'].to_s,
|
|
27
|
+
:payload_args => payload['args'].inspect
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module ResqueSqs
|
|
2
|
+
module Failure
|
|
3
|
+
# All Failure classes are expected to subclass Base.
|
|
4
|
+
#
|
|
5
|
+
# When a job fails, a new instance of your Failure backend is created
|
|
6
|
+
# and #save is called.
|
|
7
|
+
class Base
|
|
8
|
+
# The exception object raised by the failed job
|
|
9
|
+
attr_accessor :exception
|
|
10
|
+
|
|
11
|
+
# The worker object who detected the failure
|
|
12
|
+
attr_accessor :worker
|
|
13
|
+
|
|
14
|
+
# The string name of the queue from which the failed job was pulled
|
|
15
|
+
attr_accessor :queue
|
|
16
|
+
|
|
17
|
+
# The payload object associated with the failed job
|
|
18
|
+
attr_accessor :payload
|
|
19
|
+
|
|
20
|
+
def initialize(exception, worker, queue, payload)
|
|
21
|
+
@exception = exception
|
|
22
|
+
@worker = worker
|
|
23
|
+
@queue = queue
|
|
24
|
+
@payload = payload
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# When a job fails, a new instance of your Failure backend is created
|
|
28
|
+
# and #save is called.
|
|
29
|
+
#
|
|
30
|
+
# This is where you POST or PUT or whatever to your Failure service.
|
|
31
|
+
def save
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The number of failures.
|
|
35
|
+
def self.count(queue = nil, class_name = nil)
|
|
36
|
+
0
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns an array of all available failure queues
|
|
40
|
+
def self.queues
|
|
41
|
+
[]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns a paginated array of failure objects.
|
|
45
|
+
def self.all(offset = 0, limit = 1, queue = nil)
|
|
46
|
+
[]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Iterate across failed objects
|
|
50
|
+
def self.each(*args)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# A URL where someone can go to view failures.
|
|
54
|
+
def self.url
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Clear all failure objects
|
|
58
|
+
def self.clear(*args)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.requeue(index)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.remove(index)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Logging!
|
|
68
|
+
def log(message)
|
|
69
|
+
@worker.log(message)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module ResqueSqs
|
|
2
|
+
module Failure
|
|
3
|
+
# A Failure backend that uses multiple backends
|
|
4
|
+
# delegates all queries to the first backend
|
|
5
|
+
class Multiple < Base
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
attr_accessor :classes
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.configure
|
|
12
|
+
yield self
|
|
13
|
+
ResqueSqs::Failure.backend = self
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(*args)
|
|
17
|
+
super
|
|
18
|
+
@backends = self.class.classes.map {|klass| klass.new(*args)}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def save
|
|
22
|
+
@backends.each(&:save)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The number of failures.
|
|
26
|
+
def self.count(*args)
|
|
27
|
+
classes.first.count(*args)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns a paginated array of failure objects.
|
|
31
|
+
def self.all(*args)
|
|
32
|
+
classes.first.all(*args)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Iterate across failed objects
|
|
36
|
+
def self.each(*args, &block)
|
|
37
|
+
classes.first.each(*args, &block)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# A URL where someone can go to view failures.
|
|
41
|
+
def self.url
|
|
42
|
+
classes.first.url
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Clear all failure objects
|
|
46
|
+
def self.clear(*args)
|
|
47
|
+
classes.first.clear(*args)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.requeue(*args)
|
|
51
|
+
classes.first.requeue(*args)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.remove(index)
|
|
55
|
+
classes.each { |klass| klass.remove(index) }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module ResqueSqs
|
|
2
|
+
module Failure
|
|
3
|
+
# A Failure backend that stores exceptions in Redis. Very simple but
|
|
4
|
+
# works out of the box, along with support in the Resque web app.
|
|
5
|
+
class Redis < Base
|
|
6
|
+
def save
|
|
7
|
+
data = {
|
|
8
|
+
:failed_at => UTF8Util.clean(Time.now.strftime("%Y/%m/%d %H:%M:%S %Z")),
|
|
9
|
+
:payload => payload,
|
|
10
|
+
:exception => exception.class.to_s,
|
|
11
|
+
:error => UTF8Util.clean(exception.to_s),
|
|
12
|
+
:backtrace => filter_backtrace(Array(exception.backtrace)),
|
|
13
|
+
:worker => worker.to_s,
|
|
14
|
+
:queue => queue
|
|
15
|
+
}
|
|
16
|
+
data = ResqueSqs.encode(data)
|
|
17
|
+
ResqueSqs.redis.rpush(:failed, data)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.count(queue = nil, class_name = nil)
|
|
21
|
+
check_queue(queue)
|
|
22
|
+
|
|
23
|
+
if class_name
|
|
24
|
+
n = 0
|
|
25
|
+
each(0, count(queue), queue, class_name) { n += 1 }
|
|
26
|
+
n
|
|
27
|
+
else
|
|
28
|
+
ResqueSqs.redis.llen(:failed).to_i
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.queues
|
|
33
|
+
[:failed]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.all(offset = 0, limit = 1, queue = nil)
|
|
37
|
+
check_queue(queue)
|
|
38
|
+
ResqueSqs.list_range(:failed, offset, limit)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.each(offset = 0, limit = self.count, queue = :failed, class_name = nil, order = 'desc')
|
|
42
|
+
all_items = Array(all(offset, limit, queue))
|
|
43
|
+
reversed = false
|
|
44
|
+
if order.eql? 'desc'
|
|
45
|
+
all_items.reverse!
|
|
46
|
+
reversed = true
|
|
47
|
+
end
|
|
48
|
+
all_items.each_with_index do |item, i|
|
|
49
|
+
if !class_name || (item['payload'] && item['payload']['class'] == class_name)
|
|
50
|
+
if reversed
|
|
51
|
+
id = (all_items.length - 1) - (offset + i)
|
|
52
|
+
else
|
|
53
|
+
id = offset + i
|
|
54
|
+
end
|
|
55
|
+
yield id, item
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.clear(queue = nil)
|
|
61
|
+
check_queue(queue)
|
|
62
|
+
ResqueSqs.redis.del(:failed)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.requeue(id)
|
|
66
|
+
item = all(id)
|
|
67
|
+
item['retried_at'] = Time.now.strftime("%Y/%m/%d %H:%M:%S")
|
|
68
|
+
ResqueSqs.redis.lset(:failed, id, ResqueSqs.encode(item))
|
|
69
|
+
Job.create(item['queue'], item['payload']['class'], *item['payload']['args'])
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.remove(id)
|
|
73
|
+
sentinel = ""
|
|
74
|
+
ResqueSqs.redis.lset(:failed, id, sentinel)
|
|
75
|
+
ResqueSqs.redis.lrem(:failed, 1, sentinel)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.requeue_queue(queue)
|
|
79
|
+
i = 0
|
|
80
|
+
while job = all(i)
|
|
81
|
+
requeue(i) if job['queue'] == queue
|
|
82
|
+
i += 1
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.remove_queue(queue)
|
|
87
|
+
i = 0
|
|
88
|
+
while job = all(i)
|
|
89
|
+
if job['queue'] == queue
|
|
90
|
+
# This will remove the failure from the array so do not increment the index.
|
|
91
|
+
remove(i)
|
|
92
|
+
else
|
|
93
|
+
i += 1
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def self.check_queue(queue)
|
|
99
|
+
raise ArgumentError, "invalid queue: #{queue}" if queue && queue.to_s != "failed"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def filter_backtrace(backtrace)
|
|
103
|
+
index = backtrace.index { |item| item.include?('/lib/resque/job.rb') }
|
|
104
|
+
backtrace.first(index.to_i)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module ResqueSqs
|
|
2
|
+
module Failure
|
|
3
|
+
# A Failure backend that stores exceptions in Redis. Very simple but
|
|
4
|
+
# works out of the box, along with support in the Resque web app.
|
|
5
|
+
class RedisMultiQueue < Base
|
|
6
|
+
def save
|
|
7
|
+
data = {
|
|
8
|
+
:failed_at => Time.now.strftime("%Y/%m/%d %H:%M:%S %Z"),
|
|
9
|
+
:payload => payload,
|
|
10
|
+
:exception => exception.class.to_s,
|
|
11
|
+
:error => UTF8Util.clean(exception.to_s),
|
|
12
|
+
:backtrace => filter_backtrace(Array(exception.backtrace)),
|
|
13
|
+
:worker => worker.to_s,
|
|
14
|
+
:queue => queue
|
|
15
|
+
}
|
|
16
|
+
data = ResqueSqs.encode(data)
|
|
17
|
+
ResqueSqs.redis.rpush(ResqueSqs::Failure.failure_queue_name(queue), data)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.count(queue = nil, class_name = nil)
|
|
21
|
+
if queue
|
|
22
|
+
if class_name
|
|
23
|
+
n = 0
|
|
24
|
+
each(0, count(queue), queue, class_name) { n += 1 }
|
|
25
|
+
n
|
|
26
|
+
else
|
|
27
|
+
ResqueSqs.redis.llen(queue).to_i
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
total = 0
|
|
31
|
+
queues.each { |q| total += count(q) }
|
|
32
|
+
total
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.all(offset = 0, limit = 1, queue = :failed, order = 'desc')
|
|
37
|
+
ResqueSqs.list_range(queue, offset, limit, order)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.queues
|
|
41
|
+
Array(ResqueSqs.redis.smembers(:failed_queues))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.each(offset = 0, limit = self.count, queue = :failed, class_name = nil, order = 'desc')
|
|
45
|
+
items = all(offset, limit, queue, order)
|
|
46
|
+
items = [items] unless items.is_a? Array
|
|
47
|
+
if order.eql? 'desc'
|
|
48
|
+
items.reverse!
|
|
49
|
+
end
|
|
50
|
+
items.each_with_index do |item, i|
|
|
51
|
+
if !class_name || (item['payload'] && item['payload']['class'] == class_name)
|
|
52
|
+
yield offset + i, item
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.clear(queue = :failed)
|
|
58
|
+
ResqueSqs.redis.del(queue)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.requeue(id, queue = :failed)
|
|
62
|
+
item = all(id, 1, queue)
|
|
63
|
+
item['retried_at'] = Time.now.strftime("%Y/%m/%d %H:%M:%S")
|
|
64
|
+
ResqueSqs.redis.lset(queue, id, ResqueSqs.encode(item))
|
|
65
|
+
Job.create(item['queue'], item['payload']['class'], *item['payload']['args'])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.remove(id, queue = :failed)
|
|
69
|
+
sentinel = ""
|
|
70
|
+
ResqueSqs.redis.lset(queue, id, sentinel)
|
|
71
|
+
ResqueSqs.redis.lrem(queue, 1, sentinel)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.requeue_queue(queue)
|
|
75
|
+
failure_queue = ResqueSqs::Failure.failure_queue_name(queue)
|
|
76
|
+
each(0, count(failure_queue), failure_queue) { |id, _| requeue(id, failure_queue) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.remove_queue(queue)
|
|
80
|
+
ResqueSqs.redis.del(ResqueSqs::Failure.failure_queue_name(queue))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def filter_backtrace(backtrace)
|
|
84
|
+
index = backtrace.index { |item| item.include?('/lib/resque/job.rb') }
|
|
85
|
+
backtrace.first(index.to_i)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
module ResqueSqs
|
|
2
|
+
# The Failure module provides an interface for working with different
|
|
3
|
+
# failure backends.
|
|
4
|
+
#
|
|
5
|
+
# You can use it to query the failure backend without knowing which specific
|
|
6
|
+
# backend is being used. For instance, the Resque web app uses it to display
|
|
7
|
+
# stats and other information.
|
|
8
|
+
module Failure
|
|
9
|
+
# Creates a new failure, which is delegated to the appropriate backend.
|
|
10
|
+
#
|
|
11
|
+
# Expects a hash with the following keys:
|
|
12
|
+
# :exception - The Exception object
|
|
13
|
+
# :worker - The Worker object who is reporting the failure
|
|
14
|
+
# :queue - The string name of the queue from which the job was pulled
|
|
15
|
+
# :payload - The job's payload
|
|
16
|
+
def self.create(options = {})
|
|
17
|
+
backend.new(*options.values_at(:exception, :worker, :queue, :payload)).save
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# Sets the current backend. Expects a class descendent of
|
|
22
|
+
# `ResqueSqs::Failure::Base`.
|
|
23
|
+
#
|
|
24
|
+
# Example use:
|
|
25
|
+
# require 'resque_sqs/failure/airbrake'
|
|
26
|
+
# ResqueSqs::Failure.backend = ResqueSqs::Failure::Airbrake
|
|
27
|
+
def self.backend=(backend)
|
|
28
|
+
@backend = backend
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns the current backend class. If none has been set, falls
|
|
32
|
+
# back to `ResqueSqs::Failure::Redis`
|
|
33
|
+
def self.backend
|
|
34
|
+
return @backend if @backend
|
|
35
|
+
|
|
36
|
+
case ENV['FAILURE_BACKEND']
|
|
37
|
+
when 'redis_multi_queue'
|
|
38
|
+
require 'resque_sqs/failure/redis_multi_queue'
|
|
39
|
+
@backend = Failure::RedisMultiQueue
|
|
40
|
+
when 'redis', nil
|
|
41
|
+
require 'resque_sqs/failure/redis'
|
|
42
|
+
@backend = Failure::Redis
|
|
43
|
+
else
|
|
44
|
+
raise ArgumentError, "invalid failure backend: #{FAILURE_BACKEND}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Obtain the failure queue name for a given job queue
|
|
49
|
+
def self.failure_queue_name(job_queue_name)
|
|
50
|
+
name = "#{job_queue_name}_failed"
|
|
51
|
+
ResqueSqs.redis.sadd(:failed_queues, name)
|
|
52
|
+
name
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Obtain the job queue name for a given failure queue
|
|
56
|
+
def self.job_queue_name(failure_queue_name)
|
|
57
|
+
failure_queue_name.sub(/_failed$/, '')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns an array of all the failed queues in the system
|
|
61
|
+
def self.queues
|
|
62
|
+
backend.queues
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns the int count of how many failures we have seen.
|
|
66
|
+
def self.count(queue = nil, class_name = nil)
|
|
67
|
+
backend.count(queue, class_name)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Returns an array of all the failures, paginated.
|
|
71
|
+
#
|
|
72
|
+
# `offset` is the int of the first item in the page, `limit` is the
|
|
73
|
+
# number of items to return.
|
|
74
|
+
def self.all(offset = 0, limit = 1, queue = nil)
|
|
75
|
+
backend.all(offset, limit, queue)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Iterate across all failures with the given options
|
|
79
|
+
def self.each(offset = 0, limit = self.count, queue = nil, class_name = nil, order = 'desc', &block)
|
|
80
|
+
backend.each(offset, limit, queue, class_name, order, &block)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# The string url of the backend's web interface, if any.
|
|
84
|
+
def self.url
|
|
85
|
+
backend.url
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Clear all failure jobs
|
|
89
|
+
def self.clear(queue = nil)
|
|
90
|
+
backend.clear(queue)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.requeue(id)
|
|
94
|
+
backend.requeue(id)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.remove(id)
|
|
98
|
+
backend.remove(id)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Requeues all failed jobs in a specific queue.
|
|
102
|
+
# Queue name should be a string.
|
|
103
|
+
def self.requeue_queue(queue)
|
|
104
|
+
backend.requeue_queue(queue)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Removes all failed jobs in a specific queue.
|
|
108
|
+
# Queue name should be a string.
|
|
109
|
+
def self.remove_queue(queue)
|
|
110
|
+
backend.remove_queue(queue)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|