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.
Files changed (64) hide show
  1. data/HISTORY.md +354 -0
  2. data/LICENSE +20 -0
  3. data/README.markdown +908 -0
  4. data/Rakefile +70 -0
  5. data/bin/resque +81 -0
  6. data/bin/resque-web +27 -0
  7. data/lib/resque.rb +385 -0
  8. data/lib/resque/coder.rb +27 -0
  9. data/lib/resque/errors.rb +10 -0
  10. data/lib/resque/failure.rb +96 -0
  11. data/lib/resque/failure/airbrake.rb +17 -0
  12. data/lib/resque/failure/base.rb +64 -0
  13. data/lib/resque/failure/hoptoad.rb +33 -0
  14. data/lib/resque/failure/multiple.rb +54 -0
  15. data/lib/resque/failure/redis.rb +51 -0
  16. data/lib/resque/failure/thoughtbot.rb +33 -0
  17. data/lib/resque/helpers.rb +64 -0
  18. data/lib/resque/job.rb +223 -0
  19. data/lib/resque/multi_json_coder.rb +37 -0
  20. data/lib/resque/multi_queue.rb +73 -0
  21. data/lib/resque/plugin.rb +66 -0
  22. data/lib/resque/queue.rb +117 -0
  23. data/lib/resque/server.rb +248 -0
  24. data/lib/resque/server/public/favicon.ico +0 -0
  25. data/lib/resque/server/public/idle.png +0 -0
  26. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  27. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  28. data/lib/resque/server/public/poll.png +0 -0
  29. data/lib/resque/server/public/ranger.js +73 -0
  30. data/lib/resque/server/public/reset.css +44 -0
  31. data/lib/resque/server/public/style.css +86 -0
  32. data/lib/resque/server/public/working.png +0 -0
  33. data/lib/resque/server/test_helper.rb +19 -0
  34. data/lib/resque/server/views/error.erb +1 -0
  35. data/lib/resque/server/views/failed.erb +67 -0
  36. data/lib/resque/server/views/key_sets.erb +19 -0
  37. data/lib/resque/server/views/key_string.erb +11 -0
  38. data/lib/resque/server/views/layout.erb +44 -0
  39. data/lib/resque/server/views/next_more.erb +10 -0
  40. data/lib/resque/server/views/overview.erb +4 -0
  41. data/lib/resque/server/views/queues.erb +49 -0
  42. data/lib/resque/server/views/stats.erb +62 -0
  43. data/lib/resque/server/views/workers.erb +109 -0
  44. data/lib/resque/server/views/working.erb +72 -0
  45. data/lib/resque/stat.rb +53 -0
  46. data/lib/resque/tasks.rb +61 -0
  47. data/lib/resque/version.rb +3 -0
  48. data/lib/resque/worker.rb +557 -0
  49. data/lib/tasks/redis.rake +161 -0
  50. data/lib/tasks/resque.rake +2 -0
  51. data/test/airbrake_test.rb +26 -0
  52. data/test/hoptoad_test.rb +26 -0
  53. data/test/job_hooks_test.rb +423 -0
  54. data/test/job_plugins_test.rb +230 -0
  55. data/test/multi_queue_test.rb +95 -0
  56. data/test/plugin_test.rb +116 -0
  57. data/test/redis-test-cluster.conf +115 -0
  58. data/test/redis-test.conf +115 -0
  59. data/test/redis_queue_test.rb +133 -0
  60. data/test/resque-web_test.rb +59 -0
  61. data/test/resque_test.rb +284 -0
  62. data/test/test_helper.rb +135 -0
  63. data/test/worker_test.rb +443 -0
  64. metadata +188 -0
@@ -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