nfo-resque-mongo 1.15.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 (55) hide show
  1. data/HISTORY.md +259 -0
  2. data/LICENSE +20 -0
  3. data/README.markdown +828 -0
  4. data/Rakefile +73 -0
  5. data/bin/resque +75 -0
  6. data/bin/resque-web +23 -0
  7. data/lib/resque/errors.rb +10 -0
  8. data/lib/resque/failure/base.rb +74 -0
  9. data/lib/resque/failure/hoptoad.rb +139 -0
  10. data/lib/resque/failure/mongo.rb +92 -0
  11. data/lib/resque/failure/multiple.rb +60 -0
  12. data/lib/resque/failure.rb +82 -0
  13. data/lib/resque/helpers.rb +79 -0
  14. data/lib/resque/job.rb +228 -0
  15. data/lib/resque/plugin.rb +51 -0
  16. data/lib/resque/queue_stats.rb +58 -0
  17. data/lib/resque/server/public/idle.png +0 -0
  18. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  19. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  20. data/lib/resque/server/public/poll.png +0 -0
  21. data/lib/resque/server/public/ranger.js +73 -0
  22. data/lib/resque/server/public/reset.css +48 -0
  23. data/lib/resque/server/public/style.css +86 -0
  24. data/lib/resque/server/public/working.png +0 -0
  25. data/lib/resque/server/test_helper.rb +19 -0
  26. data/lib/resque/server/views/error.erb +1 -0
  27. data/lib/resque/server/views/failed.erb +75 -0
  28. data/lib/resque/server/views/key_sets.erb +19 -0
  29. data/lib/resque/server/views/key_string.erb +11 -0
  30. data/lib/resque/server/views/layout.erb +38 -0
  31. data/lib/resque/server/views/next_more.erb +19 -0
  32. data/lib/resque/server/views/overview.erb +4 -0
  33. data/lib/resque/server/views/queues.erb +49 -0
  34. data/lib/resque/server/views/stats.erb +62 -0
  35. data/lib/resque/server/views/workers.erb +109 -0
  36. data/lib/resque/server/views/working.erb +68 -0
  37. data/lib/resque/server.rb +222 -0
  38. data/lib/resque/stat.rb +55 -0
  39. data/lib/resque/tasks.rb +42 -0
  40. data/lib/resque/version.rb +3 -0
  41. data/lib/resque/worker.rb +524 -0
  42. data/lib/resque.rb +384 -0
  43. data/tasks/redis.rake +161 -0
  44. data/tasks/resque.rake +2 -0
  45. data/test/dump.rdb +0 -0
  46. data/test/job_hooks_test.rb +323 -0
  47. data/test/job_plugins_test.rb +230 -0
  48. data/test/plugin_test.rb +116 -0
  49. data/test/queue_stats_test.rb +57 -0
  50. data/test/redis-test.conf +115 -0
  51. data/test/resque-web_test.rb +48 -0
  52. data/test/resque_test.rb +256 -0
  53. data/test/test_helper.rb +151 -0
  54. data/test/worker_test.rb +356 -0
  55. metadata +166 -0
@@ -0,0 +1,60 @@
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
+ # Number of result of a failures search.
31
+ def self.search_count
32
+ classes.first.search_count if classes.first.respond_to?(:search_count)
33
+ end
34
+
35
+ # Returns a paginated array of failure objects.
36
+ def self.all(start = 0, count = 1)
37
+ classes.first.all(start,count)
38
+ end
39
+
40
+ # The results of a failures search.
41
+ def self.search_results(query, start = 0, count = 1)
42
+ classes.first.search_results(query, start, count) if classes.first.respond_to?(:search_results)
43
+ end
44
+
45
+ # A URL where someone can go to view failures.
46
+ def self.url
47
+ classes.first.url
48
+ end
49
+
50
+ # Clear all failure objects
51
+ def self.clear
52
+ classes.first.clear
53
+ end
54
+
55
+ def self.requeue(*args)
56
+ classes.first.requeue(*args)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,82 @@
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/mongo'
36
+ @backend = Failure::Mongo
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 the int count of the number of results for the last search executed
45
+ def self.search_count
46
+ backend.search_count
47
+ end
48
+
49
+ # Returns an array of all the failures, paginated.
50
+ #
51
+ # `start` is the int of the first item in the page, `count` is the
52
+ # number of items to return.
53
+ def self.all(start = 0, count = 1)
54
+ backend.all(start, count)
55
+ end
56
+
57
+ # Will hold the last count - start results of
58
+ # a search through all the failed jobs,
59
+ # using the query string 'squery'
60
+ def self.search_results(start = 0, count = 1, squery)
61
+ backend.search_results(start, count, squery)
62
+ end
63
+
64
+ # The string url of the backend's web interface, if any.
65
+ def self.url
66
+ backend.url
67
+ end
68
+
69
+ # Clear all failure jobs
70
+ def self.clear
71
+ backend.clear
72
+ end
73
+
74
+ def self.requeue(index)
75
+ backend.requeue(index)
76
+ end
77
+
78
+ def self.remove(index)
79
+ backend.remove(index)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,79 @@
1
+ module Resque
2
+ # Methods used by various classes in Resque.
3
+ module Helpers
4
+ class DecodeException < StandardError; end
5
+
6
+ # Direct access to the Redis instance.
7
+ def mongo
8
+ Resque.mongo
9
+ end
10
+
11
+ def mongo_workers
12
+ Resque.mongo_workers
13
+ end
14
+
15
+ def mongo_stats
16
+ Resque.mongo_stats
17
+ end
18
+
19
+ def mongo_queues
20
+ Resque.mongo_queues
21
+ end
22
+
23
+ # Given a Ruby object, returns a string suitable for storage in a
24
+ # queue.
25
+ def encode(object)
26
+ if defined? Yajl
27
+ Yajl::Encoder.encode(object)
28
+ else
29
+ object.to_json
30
+ end
31
+ end
32
+
33
+ # Given a string, returns a Ruby object.
34
+ def decode(object)
35
+ return unless object
36
+
37
+ if defined? Yajl
38
+ begin
39
+ Yajl::Parser.parse(object, :check_utf8 => false)
40
+ rescue Yajl::ParseError => e
41
+ raise DecodeException, e
42
+ end
43
+ else
44
+ begin
45
+ JSON.parse(object)
46
+ rescue JSON::ParserError => e
47
+ raise DecodeException, e
48
+ end
49
+ end
50
+ end
51
+
52
+ # Given a word with dashes, returns a camel cased version of it.
53
+ #
54
+ # classify('job-name') # => 'JobName'
55
+ def classify(dashed_word)
56
+ dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
57
+ end
58
+
59
+ # Given a camel cased word, returns the constant it represents
60
+ #
61
+ # constantize('JobName') # => JobName
62
+ def constantize(camel_cased_word)
63
+ camel_cased_word = camel_cased_word.to_s
64
+
65
+ if camel_cased_word.include?('-')
66
+ camel_cased_word = classify(camel_cased_word)
67
+ end
68
+
69
+ names = camel_cased_word.split('::')
70
+ names.shift if names.empty? || names.first.empty?
71
+
72
+ constant = Object
73
+ names.each do |name|
74
+ constant = constant.const_get(name) || constant.const_missing(name)
75
+ end
76
+ constant
77
+ end
78
+ end
79
+ end
data/lib/resque/job.rb ADDED
@@ -0,0 +1,228 @@
1
+ module Resque
2
+ # A Resque::Job represents a unit of work. Each job lives on a
3
+ # single queue and has an associated payload object. The payload
4
+ # is a hash with two attributes: `class` and `args`. The `class` is
5
+ # the name of the Ruby class which should be used to run the
6
+ # job. The `args` are an array of arguments which should be passed
7
+ # to the Ruby class's `perform` class-level method.
8
+ #
9
+ # You can manually run a job using this code:
10
+ #
11
+ # job = Resque::Job.reserve(:high)
12
+ # klass = Resque::Job.constantize(job.payload['class'])
13
+ # klass.perform(*job.payload['args'])
14
+ class Job
15
+ include Helpers
16
+ extend Helpers
17
+
18
+ # Raise Resque::Job::DontPerform from a before_perform hook to
19
+ # abort the job.
20
+ DontPerform = Class.new(StandardError)
21
+
22
+ # The worker object which is currently processing this job.
23
+ attr_accessor :worker
24
+
25
+ # The name of the queue from which this job was pulled (or is to be
26
+ # placed)
27
+ attr_reader :queue
28
+
29
+ # This job's associated payload object.
30
+ attr_reader :payload
31
+
32
+ attr_reader :start
33
+
34
+ def initialize(queue, payload, start = Time.now)
35
+ @queue = queue
36
+ @payload = payload
37
+ @start = start
38
+ end
39
+
40
+ # Creates a job by placing it on a queue. Expects a string queue
41
+ # name, a string class name, and an optional array of arguments to
42
+ # pass to the class' `perform` method.
43
+ #
44
+ # Raises an exception if no queue or class is given.
45
+ def self.create(queue, klass, *args)
46
+ Resque.validate(klass, queue)
47
+
48
+ if Resque.inline?
49
+ constantize(klass).perform(*decode(encode(args)))
50
+ else
51
+ Resque.push(queue, :class => klass.to_s, :args => args)
52
+ end
53
+ end
54
+
55
+ def self.from_hash(hash)
56
+ new(hash['queue'],hash['payload'],Time.parse(hash['run_at']))
57
+ end
58
+
59
+ # Removes a job from a queue. Expects a string queue name, a
60
+ # string class name, and, optionally, args.
61
+ #
62
+ # Returns the number of jobs destroyed.
63
+ #
64
+ # If no args are provided, it will remove all jobs of the class
65
+ # provided.
66
+ #
67
+ # That is, for these two jobs:
68
+ #
69
+ # { 'class' => 'UpdateGraph', 'args' => ['defunkt'] }
70
+ # { 'class' => 'UpdateGraph', 'args' => ['mojombo'] }
71
+ #
72
+ # The following call will remove both:
73
+ #
74
+ # Resque::Job.destroy(queue, 'UpdateGraph')
75
+ #
76
+ # Whereas specifying args will only remove the 2nd job:
77
+ #
78
+ # Resque::Job.destroy(queue, 'UpdateGraph', 'mojombo')
79
+ #
80
+ # This method can be potentially very slow and memory intensive,
81
+ # depending on the size of your queue, as it loads all jobs into
82
+ # a Ruby array before processing.
83
+ def self.destroy(queue, klass, *args)
84
+ klass = klass.to_s
85
+
86
+ destroyed = 0
87
+
88
+ ### find the item using a mongo query, that's essentially why we removed decode / encode
89
+ ### for the job payload. This should be much faster than loading every job and matching
90
+ ### them against our conditions !
91
+
92
+ mongo_query = { :queue => queue.to_s, 'item.class' => klass }
93
+ unless args.empty?
94
+ mongo_query.merge!({ 'item.args' => args })
95
+ end
96
+ job_count = mongo.find(mongo_query).count
97
+ mongo.remove(mongo_query, :safe => true)
98
+ QueueStats.remove_job(queue,job_count)
99
+ job_count
100
+
101
+ # mongo.find(mongo_query).each do |rec|
102
+ # json = decode(rec['item'])
103
+ #
104
+ # match = json['class'] == klass
105
+ # match &= json['args'] == args unless args.empty?
106
+ #
107
+ # if match
108
+ # destroyed += 1
109
+ # mongo.remove(:_id => rec['_id'])
110
+ # end
111
+ # end
112
+ #
113
+ # destroyed
114
+ end
115
+
116
+ # Given a string queue name, returns an instance of Resque::Job
117
+ # if any jobs are available. If not, returns nil.
118
+ def self.reserve(queue)
119
+ return unless payload = Resque.pop(queue)
120
+ new(queue, payload)
121
+ end
122
+
123
+ # Attempts to perform the work represented by this job instance.
124
+ # Calls #perform on the class given in the payload with the
125
+ # arguments given in the payload.
126
+ def perform
127
+ job = payload_class
128
+ job_args = args || []
129
+ job_was_performed = false
130
+
131
+ before_hooks = Plugin.before_hooks(job)
132
+ around_hooks = Plugin.around_hooks(job)
133
+ after_hooks = Plugin.after_hooks(job)
134
+ failure_hooks = Plugin.failure_hooks(job)
135
+
136
+ begin
137
+ # Execute before_perform hook. Abort the job gracefully if
138
+ # Resque::DontPerform is raised.
139
+ begin
140
+ before_hooks.each do |hook|
141
+ job.send(hook, *job_args)
142
+ end
143
+ rescue DontPerform
144
+ return false
145
+ end
146
+
147
+ # Execute the job. Do it in an around_perform hook if available.
148
+ if around_hooks.empty?
149
+ job.perform(*job_args)
150
+ job_was_performed = true
151
+ else
152
+ # We want to nest all around_perform plugins, with the last one
153
+ # finally calling perform
154
+ stack = around_hooks.reverse.inject(nil) do |last_hook, hook|
155
+ if last_hook
156
+ lambda do
157
+ job.send(hook, *job_args) { last_hook.call }
158
+ end
159
+ else
160
+ lambda do
161
+ job.send(hook, *job_args) do
162
+ result = job.perform(*job_args)
163
+ job_was_performed = true
164
+ result
165
+ end
166
+ end
167
+ end
168
+ end
169
+ stack.call
170
+ end
171
+
172
+ # Execute after_perform hook
173
+ after_hooks.each do |hook|
174
+ job.send(hook, *job_args)
175
+ end
176
+
177
+ # Return true if the job was performed
178
+ return job_was_performed
179
+
180
+ # If an exception occurs during the job execution, look for an
181
+ # on_failure hook then re-raise.
182
+ rescue Object => e
183
+ failure_hooks.each { |hook| job.send(hook, e, *job_args) }
184
+ raise e
185
+ end
186
+ end
187
+
188
+ # Returns the actual class constant represented in this job's payload.
189
+ def payload_class
190
+ @payload_class ||= constantize(@payload['class'])
191
+ end
192
+
193
+ # Returns an array of args represented in this job's payload.
194
+ def args
195
+ @payload['args']
196
+ end
197
+
198
+ # Given an exception object, hands off the needed parameters to
199
+ # the Failure module.
200
+ def fail(exception)
201
+ Failure.create \
202
+ :payload => payload,
203
+ :exception => exception,
204
+ :worker => worker,
205
+ :queue => queue
206
+ end
207
+
208
+ # Creates an identical job, essentially placing this job back on
209
+ # the queue.
210
+ def recreate
211
+ self.class.create(queue, payload_class, *args)
212
+ end
213
+
214
+ # String representation
215
+ def inspect
216
+ obj = @payload
217
+ "(Job{%s} | %s | %s)" % [ @queue, obj['class'], obj['args'].inspect ]
218
+ end
219
+
220
+ # Equality
221
+ def ==(other)
222
+ queue == other.queue &&
223
+ payload_class == other.payload_class &&
224
+ args == other.args
225
+ end
226
+
227
+ end
228
+ end