resque_sqs 1.25.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|