tr_resque 1.20.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) 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 +369 -0
  8. data/lib/resque/errors.rb +10 -0
  9. data/lib/resque/failure.rb +96 -0
  10. data/lib/resque/failure/airbrake.rb +17 -0
  11. data/lib/resque/failure/base.rb +64 -0
  12. data/lib/resque/failure/hoptoad.rb +33 -0
  13. data/lib/resque/failure/multiple.rb +54 -0
  14. data/lib/resque/failure/redis.rb +51 -0
  15. data/lib/resque/failure/thoughtbot.rb +33 -0
  16. data/lib/resque/helpers.rb +94 -0
  17. data/lib/resque/job.rb +227 -0
  18. data/lib/resque/plugin.rb +66 -0
  19. data/lib/resque/server.rb +248 -0
  20. data/lib/resque/server/public/favicon.ico +0 -0
  21. data/lib/resque/server/public/idle.png +0 -0
  22. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  23. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  24. data/lib/resque/server/public/poll.png +0 -0
  25. data/lib/resque/server/public/ranger.js +73 -0
  26. data/lib/resque/server/public/reset.css +44 -0
  27. data/lib/resque/server/public/style.css +86 -0
  28. data/lib/resque/server/public/working.png +0 -0
  29. data/lib/resque/server/test_helper.rb +19 -0
  30. data/lib/resque/server/views/error.erb +1 -0
  31. data/lib/resque/server/views/failed.erb +67 -0
  32. data/lib/resque/server/views/key_sets.erb +19 -0
  33. data/lib/resque/server/views/key_string.erb +11 -0
  34. data/lib/resque/server/views/layout.erb +44 -0
  35. data/lib/resque/server/views/next_more.erb +10 -0
  36. data/lib/resque/server/views/overview.erb +4 -0
  37. data/lib/resque/server/views/queues.erb +49 -0
  38. data/lib/resque/server/views/stats.erb +62 -0
  39. data/lib/resque/server/views/workers.erb +109 -0
  40. data/lib/resque/server/views/working.erb +72 -0
  41. data/lib/resque/stat.rb +53 -0
  42. data/lib/resque/tasks.rb +61 -0
  43. data/lib/resque/version.rb +3 -0
  44. data/lib/resque/worker.rb +546 -0
  45. data/lib/tasks/redis.rake +161 -0
  46. data/lib/tasks/resque.rake +2 -0
  47. data/test/airbrake_test.rb +27 -0
  48. data/test/hoptoad_test.rb +26 -0
  49. data/test/job_hooks_test.rb +423 -0
  50. data/test/job_plugins_test.rb +230 -0
  51. data/test/plugin_test.rb +116 -0
  52. data/test/redis-test-cluster.conf +115 -0
  53. data/test/redis-test.conf +115 -0
  54. data/test/resque-web_test.rb +59 -0
  55. data/test/resque_test.rb +278 -0
  56. data/test/test_helper.rb +160 -0
  57. data/test/worker_test.rb +434 -0
  58. metadata +186 -0
@@ -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,94 @@
1
+ require 'multi_json'
2
+
3
+ # OkJson won't work because it doesn't serialize symbols
4
+ # in the same way yajl and json do.
5
+ if MultiJson.respond_to?(:adapter)
6
+ raise "Please install the yajl-ruby or json gem" if MultiJson.adapter.to_s == 'MultiJson::Adapters::OkJson'
7
+ elsif MultiJson.respond_to?(:engine)
8
+ raise "Please install the yajl-ruby or json gem" if MultiJson.engine.to_s == 'MultiJson::Engines::OkJson'
9
+ end
10
+
11
+ module Resque
12
+ # Methods used by various classes in Resque.
13
+ module Helpers
14
+ class DecodeException < StandardError; end
15
+
16
+ # Direct access to the Redis instance.
17
+ def redis
18
+ Resque.redis
19
+ end
20
+
21
+ # Given a Ruby object, returns a string suitable for storage in a
22
+ # queue.
23
+ def encode(object)
24
+ if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
25
+ MultiJson.dump object
26
+ else
27
+ MultiJson.encode object
28
+ end
29
+
30
+ end
31
+
32
+ # Given a string, returns a Ruby object.
33
+ def decode(object)
34
+ return unless object
35
+
36
+ begin
37
+ if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
38
+ MultiJson.load object
39
+ else
40
+ MultiJson.decode object
41
+ end
42
+ rescue ::MultiJson::DecodeError => e
43
+ raise DecodeException, e.message, e.backtrace
44
+ end
45
+ end
46
+
47
+ # Given a word with dashes, returns a camel cased version of it.
48
+ #
49
+ # classify('job-name') # => 'JobName'
50
+ def classify(dashed_word)
51
+ dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
52
+ end
53
+
54
+ # Tries to find a constant with the name specified in the argument string:
55
+ #
56
+ # constantize("Module") # => Module
57
+ # constantize("Test::Unit") # => Test::Unit
58
+ #
59
+ # The name is assumed to be the one of a top-level constant, no matter
60
+ # whether it starts with "::" or not. No lexical context is taken into
61
+ # account:
62
+ #
63
+ # C = 'outside'
64
+ # module M
65
+ # C = 'inside'
66
+ # C # => 'inside'
67
+ # constantize("C") # => 'outside', same as ::C
68
+ # end
69
+ #
70
+ # NameError is raised when the constant is unknown.
71
+ def constantize(camel_cased_word)
72
+ camel_cased_word = camel_cased_word.to_s
73
+
74
+ if camel_cased_word.include?('-')
75
+ camel_cased_word = classify(camel_cased_word)
76
+ end
77
+
78
+ names = camel_cased_word.split('::')
79
+ names.shift if names.empty? || names.first.empty?
80
+
81
+ constant = Object
82
+ names.each do |name|
83
+ args = Module.method(:const_get).arity != 1 ? [false] : []
84
+
85
+ if constant.const_defined?(name, *args)
86
+ constant = constant.const_get(name)
87
+ else
88
+ constant = constant.const_missing(name)
89
+ end
90
+ end
91
+ constant
92
+ end
93
+ end
94
+ end