resque-cedar 1.20.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.
- data/HISTORY.md +354 -0
- data/LICENSE +20 -0
- data/README.markdown +908 -0
- data/Rakefile +70 -0
- data/bin/resque +81 -0
- data/bin/resque-web +27 -0
- data/lib/resque.rb +385 -0
- data/lib/resque/coder.rb +27 -0
- data/lib/resque/errors.rb +10 -0
- data/lib/resque/failure.rb +96 -0
- data/lib/resque/failure/airbrake.rb +17 -0
- data/lib/resque/failure/base.rb +64 -0
- data/lib/resque/failure/hoptoad.rb +33 -0
- data/lib/resque/failure/multiple.rb +54 -0
- data/lib/resque/failure/redis.rb +51 -0
- data/lib/resque/failure/thoughtbot.rb +33 -0
- data/lib/resque/helpers.rb +64 -0
- data/lib/resque/job.rb +223 -0
- data/lib/resque/multi_json_coder.rb +37 -0
- data/lib/resque/multi_queue.rb +73 -0
- data/lib/resque/plugin.rb +66 -0
- data/lib/resque/queue.rb +117 -0
- data/lib/resque/server.rb +248 -0
- data/lib/resque/server/public/favicon.ico +0 -0
- data/lib/resque/server/public/idle.png +0 -0
- data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
- data/lib/resque/server/public/jquery.relatize_date.js +95 -0
- data/lib/resque/server/public/poll.png +0 -0
- data/lib/resque/server/public/ranger.js +73 -0
- data/lib/resque/server/public/reset.css +44 -0
- data/lib/resque/server/public/style.css +86 -0
- data/lib/resque/server/public/working.png +0 -0
- data/lib/resque/server/test_helper.rb +19 -0
- data/lib/resque/server/views/error.erb +1 -0
- data/lib/resque/server/views/failed.erb +67 -0
- data/lib/resque/server/views/key_sets.erb +19 -0
- data/lib/resque/server/views/key_string.erb +11 -0
- data/lib/resque/server/views/layout.erb +44 -0
- data/lib/resque/server/views/next_more.erb +10 -0
- data/lib/resque/server/views/overview.erb +4 -0
- data/lib/resque/server/views/queues.erb +49 -0
- data/lib/resque/server/views/stats.erb +62 -0
- data/lib/resque/server/views/workers.erb +109 -0
- data/lib/resque/server/views/working.erb +72 -0
- data/lib/resque/stat.rb +53 -0
- data/lib/resque/tasks.rb +61 -0
- data/lib/resque/version.rb +3 -0
- data/lib/resque/worker.rb +557 -0
- data/lib/tasks/redis.rake +161 -0
- data/lib/tasks/resque.rake +2 -0
- data/test/airbrake_test.rb +26 -0
- data/test/hoptoad_test.rb +26 -0
- data/test/job_hooks_test.rb +423 -0
- data/test/job_plugins_test.rb +230 -0
- data/test/multi_queue_test.rb +95 -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/redis_queue_test.rb +133 -0
- data/test/resque-web_test.rb +59 -0
- data/test/resque_test.rb +284 -0
- data/test/test_helper.rb +135 -0
- data/test/worker_test.rb +443 -0
- metadata +188 -0
data/lib/resque/coder.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Resque
|
2
|
+
class EncodeException < StandardError; end
|
3
|
+
class DecodeException < StandardError; end
|
4
|
+
|
5
|
+
class Coder
|
6
|
+
# Given a Ruby object, returns a string suitable for storage in a
|
7
|
+
# queue.
|
8
|
+
def encode(object)
|
9
|
+
raise EncodeException
|
10
|
+
end
|
11
|
+
|
12
|
+
# alias for encode
|
13
|
+
def dump(object)
|
14
|
+
encode(object)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Given a string, returns a Ruby object.
|
18
|
+
def decode(object)
|
19
|
+
raise DecodeException
|
20
|
+
end
|
21
|
+
|
22
|
+
# alias for decode
|
23
|
+
def load(object)
|
24
|
+
decode(object)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Resque
|
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
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Resque
|
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
|
+
# `Resque::Failure::Base`.
|
23
|
+
#
|
24
|
+
# Example use:
|
25
|
+
# require 'resque/failure/hoptoad'
|
26
|
+
# Resque::Failure.backend = Resque::Failure::Hoptoad
|
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 `Resque::Failure::Redis`
|
33
|
+
def self.backend
|
34
|
+
return @backend if @backend
|
35
|
+
require 'resque/failure/redis'
|
36
|
+
@backend = Failure::Redis
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the int count of how many failures we have seen.
|
40
|
+
def self.count
|
41
|
+
backend.count
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns an array of all the failures, paginated.
|
45
|
+
#
|
46
|
+
# `start` is the int of the first item in the page, `count` is the
|
47
|
+
# number of items to return.
|
48
|
+
def self.all(start = 0, count = 1)
|
49
|
+
backend.all(start, count)
|
50
|
+
end
|
51
|
+
|
52
|
+
# The string url of the backend's web interface, if any.
|
53
|
+
def self.url
|
54
|
+
backend.url
|
55
|
+
end
|
56
|
+
|
57
|
+
# Clear all failure jobs
|
58
|
+
def self.clear
|
59
|
+
backend.clear
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.requeue(index)
|
63
|
+
backend.requeue(index)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.remove(index)
|
67
|
+
backend.remove(index)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Requeues all failed jobs in a specific queue.
|
71
|
+
# Queue name should be a string.
|
72
|
+
def self.requeue_queue(queue)
|
73
|
+
i=0
|
74
|
+
while job = Resque::Failure.all(i)
|
75
|
+
if job['queue'] == queue
|
76
|
+
Resque::Failure.requeue(i)
|
77
|
+
end
|
78
|
+
i+=1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Removes all failed jobs in a specific queue.
|
83
|
+
# Queue name should be a string.
|
84
|
+
def self.remove_queue(queue)
|
85
|
+
i=0
|
86
|
+
while job = Resque::Failure.all(i)
|
87
|
+
if job['queue'] == queue
|
88
|
+
# This will remove the failure from the array so do not increment the index.
|
89
|
+
Resque::Failure.remove(i)
|
90
|
+
else
|
91
|
+
i+=1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,17 @@
|
|
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
|
+
require 'resque/failure/thoughtbot'
|
8
|
+
|
9
|
+
module Resque
|
10
|
+
module Failure
|
11
|
+
class Airbrake < Base
|
12
|
+
include Resque::Failure::Thoughtbot
|
13
|
+
|
14
|
+
@klass = ::Airbrake
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Resque
|
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
|
36
|
+
0
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a paginated array of failure objects.
|
40
|
+
def self.all(start = 0, count = 1)
|
41
|
+
[]
|
42
|
+
end
|
43
|
+
|
44
|
+
# A URL where someone can go to view failures.
|
45
|
+
def self.url
|
46
|
+
end
|
47
|
+
|
48
|
+
# Clear all failure objects
|
49
|
+
def self.clear
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.requeue(index)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.remove(index)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Logging!
|
59
|
+
def log(message)
|
60
|
+
@worker.log(message)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
begin
|
2
|
+
require 'hoptoad_notifier'
|
3
|
+
rescue LoadError
|
4
|
+
raise "Can't find 'hoptoad_notifier' gem. Please add it to your Gemfile or install it."
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'resque/failure/thoughtbot'
|
8
|
+
|
9
|
+
module Resque
|
10
|
+
module Failure
|
11
|
+
# A Failure backend that sends exceptions raised by jobs to Hoptoad.
|
12
|
+
#
|
13
|
+
# To use it, put this code in an initializer, Rake task, or wherever:
|
14
|
+
#
|
15
|
+
# require 'resque/failure/hoptoad'
|
16
|
+
#
|
17
|
+
# Resque::Failure::Multiple.classes = [Resque::Failure::Redis, Resque::Failure::Hoptoad]
|
18
|
+
# Resque::Failure.backend = Resque::Failure::Multiple
|
19
|
+
#
|
20
|
+
# Once you've configured resque to use the Hoptoad failure backend,
|
21
|
+
# you'll want to setup an initializer to configure the Hoptoad.
|
22
|
+
#
|
23
|
+
# HoptoadNotifier.configure do |config|
|
24
|
+
# config.api_key = 'your_key_here'
|
25
|
+
# end
|
26
|
+
# For more information see https://github.com/thoughtbot/hoptoad_notifier
|
27
|
+
class Hoptoad < Base
|
28
|
+
include Resque::Failure::Thoughtbot
|
29
|
+
|
30
|
+
@klass = ::HoptoadNotifier
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Resque
|
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
|
+
Resque::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
|
27
|
+
classes.first.count
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns a paginated array of failure objects.
|
31
|
+
def self.all(start = 0, count = 1)
|
32
|
+
classes.first.all(start,count)
|
33
|
+
end
|
34
|
+
|
35
|
+
# A URL where someone can go to view failures.
|
36
|
+
def self.url
|
37
|
+
classes.first.url
|
38
|
+
end
|
39
|
+
|
40
|
+
# Clear all failure objects
|
41
|
+
def self.clear
|
42
|
+
classes.first.clear
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.requeue(*args)
|
46
|
+
classes.first.requeue(*args)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.remove(index)
|
50
|
+
classes.each { |klass| klass.remove(index) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Resque
|
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 => Time.now.strftime("%Y/%m/%d %H:%M:%S %Z"),
|
9
|
+
:payload => payload,
|
10
|
+
:exception => exception.class.to_s,
|
11
|
+
:error => exception.to_s,
|
12
|
+
:backtrace => filter_backtrace(Array(exception.backtrace)),
|
13
|
+
:worker => worker.to_s,
|
14
|
+
:queue => queue
|
15
|
+
}
|
16
|
+
data = Resque.encode(data)
|
17
|
+
Resque.redis.rpush(:failed, data)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.count
|
21
|
+
Resque.redis.llen(:failed).to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.all(start = 0, count = 1)
|
25
|
+
Resque.list_range(:failed, start, count)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.clear
|
29
|
+
Resque.redis.del(:failed)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.requeue(index)
|
33
|
+
item = all(index)
|
34
|
+
item['retried_at'] = Time.now.strftime("%Y/%m/%d %H:%M:%S")
|
35
|
+
Resque.redis.lset(:failed, index, Resque.encode(item))
|
36
|
+
Job.create(item['queue'], item['payload']['class'], *item['payload']['args'])
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.remove(index)
|
40
|
+
id = rand(0xffffff)
|
41
|
+
Resque.redis.lset(:failed, index, id)
|
42
|
+
Resque.redis.lrem(:failed, 1, id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def filter_backtrace(backtrace)
|
46
|
+
index = backtrace.index { |item| item.include?('/lib/resque/job.rb') }
|
47
|
+
backtrace.first(index.to_i)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Resque
|
2
|
+
module Failure
|
3
|
+
module Thoughtbot
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
attr_accessor :klass
|
10
|
+
|
11
|
+
def configure(&block)
|
12
|
+
Resque::Failure.backend = self
|
13
|
+
klass.configure(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def count
|
17
|
+
# We can't get the total # of errors from Hoptoad so we fake it
|
18
|
+
# by asking Resque how many errors it has seen.
|
19
|
+
Stat[:failed]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def save
|
24
|
+
self.class.klass.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,64 @@
|
|
1
|
+
module Resque
|
2
|
+
# Methods used by various classes in Resque.
|
3
|
+
module Helpers
|
4
|
+
# Direct access to the Redis instance.
|
5
|
+
def redis
|
6
|
+
Resque.redis
|
7
|
+
end
|
8
|
+
|
9
|
+
def encode(object)
|
10
|
+
Resque.coder.encode(object)
|
11
|
+
end
|
12
|
+
|
13
|
+
def decode(object)
|
14
|
+
Resque.coder.decode(object)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Given a word with dashes, returns a camel cased version of it.
|
18
|
+
#
|
19
|
+
# classify('job-name') # => 'JobName'
|
20
|
+
def classify(dashed_word)
|
21
|
+
dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
|
22
|
+
end
|
23
|
+
|
24
|
+
# Tries to find a constant with the name specified in the argument string:
|
25
|
+
#
|
26
|
+
# constantize("Module") # => Module
|
27
|
+
# constantize("Test::Unit") # => Test::Unit
|
28
|
+
#
|
29
|
+
# The name is assumed to be the one of a top-level constant, no matter
|
30
|
+
# whether it starts with "::" or not. No lexical context is taken into
|
31
|
+
# account:
|
32
|
+
#
|
33
|
+
# C = 'outside'
|
34
|
+
# module M
|
35
|
+
# C = 'inside'
|
36
|
+
# C # => 'inside'
|
37
|
+
# constantize("C") # => 'outside', same as ::C
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# NameError is raised when the constant is unknown.
|
41
|
+
def constantize(camel_cased_word)
|
42
|
+
camel_cased_word = camel_cased_word.to_s
|
43
|
+
|
44
|
+
if camel_cased_word.include?('-')
|
45
|
+
camel_cased_word = classify(camel_cased_word)
|
46
|
+
end
|
47
|
+
|
48
|
+
names = camel_cased_word.split('::')
|
49
|
+
names.shift if names.empty? || names.first.empty?
|
50
|
+
|
51
|
+
constant = Object
|
52
|
+
names.each do |name|
|
53
|
+
args = Module.method(:const_get).arity != 1 ? [false] : []
|
54
|
+
|
55
|
+
if constant.const_defined?(name, *args)
|
56
|
+
constant = constant.const_get(name)
|
57
|
+
else
|
58
|
+
constant = constant.const_missing(name)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
constant
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|