resque-mongo 1.4.0 → 1.8.1
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/CONTRIBUTORS +24 -6
- data/HISTORY.md +65 -0
- data/README.markdown +34 -5
- data/Rakefile +1 -1
- data/bin/resque +2 -2
- data/bin/resque-web +6 -1
- data/deps.rip +2 -2
- data/docs/HOOKS.md +121 -0
- data/docs/PLUGINS.md +93 -0
- data/examples/demo/Rakefile +5 -0
- data/examples/monit/resque.monit +6 -0
- data/lib/resque.rb +94 -7
- data/lib/resque/errors.rb +3 -0
- data/lib/resque/failure.rb +3 -0
- data/lib/resque/failure/base.rb +3 -0
- data/lib/resque/failure/hoptoad.rb +29 -19
- data/lib/resque/failure/mongo.rb +10 -1
- data/lib/resque/helpers.rb +8 -2
- data/lib/resque/job.rb +107 -2
- data/lib/resque/plugin.rb +46 -0
- data/lib/resque/server.rb +30 -11
- data/lib/resque/server/public/ranger.js +50 -7
- data/lib/resque/server/public/style.css +8 -1
- data/lib/resque/server/test_helper.rb +19 -0
- data/lib/resque/server/views/failed.erb +17 -3
- data/lib/resque/server/views/key_sets.erb +20 -0
- data/lib/resque/server/views/{key.erb → key_string.erb} +2 -8
- data/lib/resque/server/views/queues.erb +5 -2
- data/lib/resque/server/views/stats.erb +2 -2
- data/lib/resque/server/views/workers.erb +1 -1
- data/lib/resque/server/views/working.erb +2 -0
- data/lib/resque/tasks.rb +1 -1
- data/lib/resque/version.rb +1 -1
- data/lib/resque/worker.rb +54 -15
- data/tasks/redis.rake +53 -29
- data/test/job_hooks_test.rb +302 -0
- data/test/job_plugins_test.rb +209 -0
- data/test/plugin_test.rb +116 -0
- data/test/resque-mongo_benchmark.rb +62 -0
- data/test/resque-web_test.rb +54 -0
- data/test/resque_test.rb +34 -0
- data/test/test_helper.rb +15 -0
- data/test/worker_test.rb +62 -2
- metadata +58 -23
data/examples/demo/Rakefile
CHANGED
@@ -0,0 +1,6 @@
|
|
1
|
+
check process resque_worker_QUEUE
|
2
|
+
with pidfile /data/APP_NAME/current/tmp/pids/resque_worker_QUEUE.pid
|
3
|
+
start program = "/bin/sh -c 'cd /data/APP_NAME/current; RAILS_ENV=production QUEUE=queue_name VERBOSE=1 nohup rake resque:work& &> log/resque_worker_QUEUE.log && echo $! > tmp/pids/resque_worker_QUEUE.pid'" as uid deploy and gid deploy
|
4
|
+
stop program = "/bin/sh -c 'cd /data/APP_NAME/current && kill -s QUIT `cat tmp/pids/resque_worker_QUEUE.pid` && rm -f tmp/pids/resque_worker_QUEUE.pid; exit 0;'"
|
5
|
+
if totalmem is greater than 300 MB for 10 cycles then restart # eating up memory?
|
6
|
+
group resque_workers
|
data/lib/resque.rb
CHANGED
@@ -6,6 +6,8 @@ rescue LoadError
|
|
6
6
|
require 'json'
|
7
7
|
end
|
8
8
|
|
9
|
+
require 'resque/version'
|
10
|
+
|
9
11
|
require 'resque/errors'
|
10
12
|
|
11
13
|
require 'resque/failure'
|
@@ -15,6 +17,7 @@ require 'resque/helpers'
|
|
15
17
|
require 'resque/stat'
|
16
18
|
require 'resque/job'
|
17
19
|
require 'resque/worker'
|
20
|
+
require 'resque/plugin'
|
18
21
|
|
19
22
|
module Resque
|
20
23
|
include Helpers
|
@@ -65,6 +68,53 @@ module Resque
|
|
65
68
|
@stats
|
66
69
|
end
|
67
70
|
|
71
|
+
# The `before_first_fork` hook will be run in the **parent** process
|
72
|
+
# only once, before forking to run the first job. Be careful- any
|
73
|
+
# changes you make will be permanent for the lifespan of the
|
74
|
+
# worker.
|
75
|
+
#
|
76
|
+
# Call with a block to set the hook.
|
77
|
+
# Call with no arguments to return the hook.
|
78
|
+
def before_first_fork(&block)
|
79
|
+
block ? (@before_first_fork = block) : @before_first_fork
|
80
|
+
end
|
81
|
+
|
82
|
+
# Set a proc that will be called in the parent process before the
|
83
|
+
# worker forks for the first time.
|
84
|
+
def before_first_fork=(before_first_fork)
|
85
|
+
@before_first_fork = before_first_fork
|
86
|
+
end
|
87
|
+
|
88
|
+
# The `before_fork` hook will be run in the **parent** process
|
89
|
+
# before every job, so be careful- any changes you make will be
|
90
|
+
# permanent for the lifespan of the worker.
|
91
|
+
#
|
92
|
+
# Call with a block to set the hook.
|
93
|
+
# Call with no arguments to return the hook.
|
94
|
+
def before_fork(&block)
|
95
|
+
block ? (@before_fork = block) : @before_fork
|
96
|
+
end
|
97
|
+
|
98
|
+
# Set the before_fork proc.
|
99
|
+
def before_fork=(before_fork)
|
100
|
+
@before_fork = before_fork
|
101
|
+
end
|
102
|
+
|
103
|
+
# The `after_fork` hook will be run in the child process and is passed
|
104
|
+
# the current job. Any changes you make, therefor, will only live as
|
105
|
+
# long as the job currently being processes.
|
106
|
+
#
|
107
|
+
# Call with a block to set the hook.
|
108
|
+
# Call with no arguments to return the hook.
|
109
|
+
def after_fork(&block)
|
110
|
+
block ? (@after_fork = block) : @after_fork
|
111
|
+
end
|
112
|
+
|
113
|
+
# Set the after_fork proc.
|
114
|
+
def after_fork=(after_fork)
|
115
|
+
@after_fork = after_fork
|
116
|
+
end
|
117
|
+
|
68
118
|
def to_s
|
69
119
|
"Mongo Client connected to #{@con.host}"
|
70
120
|
end
|
@@ -98,9 +148,9 @@ module Resque
|
|
98
148
|
#
|
99
149
|
# Returns a Ruby object.
|
100
150
|
def pop(queue)
|
101
|
-
doc = mongo.
|
102
|
-
|
103
|
-
|
151
|
+
doc = mongo.find_and_modify( :query => { :queue => queue },
|
152
|
+
:sort => [:natural, :desc],
|
153
|
+
:remove => true )
|
104
154
|
decode doc['item']
|
105
155
|
rescue Mongo::OperationFailure => e
|
106
156
|
return nil if e.message =~ /No matching object/
|
@@ -122,6 +172,7 @@ module Resque
|
|
122
172
|
# To get the 3rd page of a 30 item, paginatied list one would use:
|
123
173
|
# Resque.peek('my_list', 59, 30)
|
124
174
|
def peek(queue, start = 0, count = 1)
|
175
|
+
start, count = [start, count].map { |n| Integer(n) }
|
125
176
|
res = mongo.find(:queue => queue).sort([:natural, :desc]).skip(start).limit(count).to_a
|
126
177
|
res.collect! { |doc| decode(doc['item']) }
|
127
178
|
|
@@ -165,13 +216,49 @@ module Resque
|
|
165
216
|
# If either of those conditions are met, it will use the value obtained
|
166
217
|
# from performing one of the above operations to determine the queue.
|
167
218
|
#
|
168
|
-
# If no queue can be inferred this method will
|
219
|
+
# If no queue can be inferred this method will raise a `Resque::NoQueueError`
|
169
220
|
#
|
170
221
|
# This method is considered part of the `stable` API.
|
171
222
|
def enqueue(klass, *args)
|
172
|
-
|
173
|
-
|
174
|
-
|
223
|
+
Job.create(queue_from_class(klass), klass, *args)
|
224
|
+
end
|
225
|
+
|
226
|
+
# This method can be used to conveniently remove a job from a queue.
|
227
|
+
# It assumes the class you're passing it is a real Ruby class (not
|
228
|
+
# a string or reference) which either:
|
229
|
+
#
|
230
|
+
# a) has a @queue ivar set
|
231
|
+
# b) responds to `queue`
|
232
|
+
#
|
233
|
+
# If either of those conditions are met, it will use the value obtained
|
234
|
+
# from performing one of the above operations to determine the queue.
|
235
|
+
#
|
236
|
+
# If no queue can be inferred this method will raise a `Resque::NoQueueError`
|
237
|
+
#
|
238
|
+
# If no args are given, this method will dequeue *all* jobs matching
|
239
|
+
# the provided class. See `Resque::Job.destroy` for more
|
240
|
+
# information.
|
241
|
+
#
|
242
|
+
# Returns the number of jobs destroyed.
|
243
|
+
#
|
244
|
+
# Example:
|
245
|
+
#
|
246
|
+
# # Removes all jobs of class `UpdateNetworkGraph`
|
247
|
+
# Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph)
|
248
|
+
#
|
249
|
+
# # Removes all jobs of class `UpdateNetworkGraph` with matching args.
|
250
|
+
# Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph, 'repo:135325')
|
251
|
+
#
|
252
|
+
# This method is considered part of the `stable` API.
|
253
|
+
def dequeue(klass, *args)
|
254
|
+
Job.destroy(queue_from_class(klass), klass, *args)
|
255
|
+
end
|
256
|
+
|
257
|
+
# Given a class, try to extrapolate an appropriate queue based on a
|
258
|
+
# class instance variable or `queue` method.
|
259
|
+
def queue_from_class(klass)
|
260
|
+
klass.instance_variable_get(:@queue) ||
|
261
|
+
(klass.respond_to?(:queue) and klass.queue)
|
175
262
|
end
|
176
263
|
|
177
264
|
# This method will return a `Resque::Job` object or a non-true value
|
data/lib/resque/errors.rb
CHANGED
data/lib/resque/failure.rb
CHANGED
data/lib/resque/failure/base.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
require 'net/
|
1
|
+
require 'net/https'
|
2
2
|
require 'builder'
|
3
|
+
require 'uri'
|
3
4
|
|
4
5
|
module Resque
|
5
6
|
module Failure
|
@@ -7,21 +8,26 @@ module Resque
|
|
7
8
|
#
|
8
9
|
# To use it, put this code in an initializer, Rake task, or wherever:
|
9
10
|
#
|
11
|
+
# require 'resque/failure/hoptoad'
|
12
|
+
#
|
10
13
|
# Resque::Failure::Hoptoad.configure do |config|
|
11
14
|
# config.api_key = 'blah'
|
12
15
|
# config.secure = true
|
13
|
-
#
|
16
|
+
#
|
17
|
+
# # optional proxy support
|
18
|
+
# config.proxy_host = 'x.y.z.t'
|
19
|
+
# config.proxy_port = 8080
|
20
|
+
#
|
21
|
+
# # server env support, defaults to RAILS_ENV or RACK_ENV
|
22
|
+
# config.server_environment = "test"
|
14
23
|
# end
|
15
24
|
class Hoptoad < Base
|
16
|
-
#
|
17
|
-
INPUT_FORMAT =
|
18
|
-
|
19
|
-
class << self
|
20
|
-
attr_accessor :secure, :api_key, :subdomain
|
21
|
-
end
|
25
|
+
# From the hoptoad plugin
|
26
|
+
INPUT_FORMAT = /^([^:]+):(\d+)(?::in `([^']+)')?$/
|
22
27
|
|
23
|
-
|
24
|
-
|
28
|
+
class << self
|
29
|
+
attr_accessor :secure, :api_key, :proxy_host, :proxy_port
|
30
|
+
attr_accessor :server_environment
|
25
31
|
end
|
26
32
|
|
27
33
|
def self.count
|
@@ -35,13 +41,12 @@ module Resque
|
|
35
41
|
Resque::Failure.backend = self
|
36
42
|
end
|
37
43
|
|
38
|
-
|
39
|
-
|
40
44
|
def save
|
41
45
|
http = use_ssl? ? :https : :http
|
42
46
|
url = URI.parse("#{http}://hoptoadapp.com/notifier_api/v2/notices")
|
43
47
|
|
44
|
-
|
48
|
+
request = Net::HTTP::Proxy(self.class.proxy_host, self.class.proxy_port)
|
49
|
+
http = request.new(url.host, url.port)
|
45
50
|
headers = {
|
46
51
|
'Content-type' => 'text/xml',
|
47
52
|
'Accept' => 'text/xml, application/xml'
|
@@ -49,7 +54,7 @@ module Resque
|
|
49
54
|
|
50
55
|
http.read_timeout = 5 # seconds
|
51
56
|
http.open_timeout = 2 # seconds
|
52
|
-
|
57
|
+
|
53
58
|
http.use_ssl = use_ssl?
|
54
59
|
|
55
60
|
begin
|
@@ -66,7 +71,7 @@ module Resque
|
|
66
71
|
log "Hoptoad Failure: #{response.class}\n#{body}"
|
67
72
|
end
|
68
73
|
end
|
69
|
-
|
74
|
+
|
70
75
|
def xml
|
71
76
|
x = Builder::XmlMarkup.new
|
72
77
|
x.instruct!
|
@@ -97,16 +102,16 @@ module Resque
|
|
97
102
|
end
|
98
103
|
end
|
99
104
|
x.tag!("server-environment") do
|
100
|
-
x.tag!("environment-name",
|
105
|
+
x.tag!("environment-name",server_environment)
|
101
106
|
end
|
102
|
-
|
107
|
+
|
103
108
|
end
|
104
109
|
end
|
105
|
-
|
110
|
+
|
106
111
|
def fill_in_backtrace_lines(x)
|
107
112
|
exception.backtrace.each do |unparsed_line|
|
108
113
|
_, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
|
109
|
-
x.line :file=>file,:number=>number
|
114
|
+
x.line :file => file,:number => number
|
110
115
|
end
|
111
116
|
end
|
112
117
|
|
@@ -117,6 +122,11 @@ module Resque
|
|
117
122
|
def api_key
|
118
123
|
self.class.api_key
|
119
124
|
end
|
125
|
+
|
126
|
+
def server_environment
|
127
|
+
return self.class.server_environment if self.class.server_environment
|
128
|
+
defined?(RAILS_ENV) ? RAILS_ENV : (ENV['RACK_ENV'] || 'development')
|
129
|
+
end
|
120
130
|
end
|
121
131
|
end
|
122
132
|
end
|
data/lib/resque/failure/mongo.rb
CHANGED
@@ -7,6 +7,7 @@ module Resque
|
|
7
7
|
data = {
|
8
8
|
:failed_at => Time.now.strftime("%Y/%m/%d %H:%M:%S"),
|
9
9
|
:payload => payload,
|
10
|
+
:exception => exception.class.to_s,
|
10
11
|
:error => exception.to_s,
|
11
12
|
:backtrace => exception.backtrace,
|
12
13
|
:worker => worker.to_s,
|
@@ -20,13 +21,21 @@ module Resque
|
|
20
21
|
end
|
21
22
|
|
22
23
|
def self.all(start = 0, count = 1)
|
23
|
-
|
24
|
+
start, count = [start, count].map { |n| Integer(n) }
|
25
|
+
all_failures = Resque.mongo_failures.find().sort([:natural, :desc]).skip(start).limit(count).to_a
|
26
|
+
all_failures.size == 1 ? all_failures.first : all_failures
|
24
27
|
end
|
25
28
|
|
26
29
|
def self.clear
|
27
30
|
Resque.mongo_failures.remove
|
28
31
|
end
|
29
32
|
|
33
|
+
def self.requeue(index)
|
34
|
+
item = all(index)
|
35
|
+
item['retried_at'] = Time.now.strftime("%Y/%m/%d %H:%M:%S")
|
36
|
+
Resque.mongo_failures.update({:_id => item['_id']}, item)
|
37
|
+
Job.create(item['queue'], item['payload']['class'], *item['payload']['args'])
|
38
|
+
end
|
30
39
|
end
|
31
40
|
end
|
32
41
|
end
|
data/lib/resque/helpers.rb
CHANGED
@@ -29,9 +29,15 @@ module Resque
|
|
29
29
|
return unless object
|
30
30
|
|
31
31
|
if defined? Yajl
|
32
|
-
|
32
|
+
begin
|
33
|
+
Yajl::Parser.parse(object, :check_utf8 => false)
|
34
|
+
rescue Yajl::ParseError
|
35
|
+
end
|
33
36
|
else
|
34
|
-
|
37
|
+
begin
|
38
|
+
JSON.parse(object)
|
39
|
+
rescue JSON::ParserError
|
40
|
+
end
|
35
41
|
end
|
36
42
|
end
|
37
43
|
|
data/lib/resque/job.rb
CHANGED
@@ -15,6 +15,10 @@ module Resque
|
|
15
15
|
include Helpers
|
16
16
|
extend Helpers
|
17
17
|
|
18
|
+
# Raise Resque::Job::DontPerform from a before_perform hook to
|
19
|
+
# abort the job.
|
20
|
+
DontPerform = Class.new(StandardError)
|
21
|
+
|
18
22
|
# The worker object which is currently processing this job.
|
19
23
|
attr_accessor :worker
|
20
24
|
|
@@ -36,7 +40,7 @@ module Resque
|
|
36
40
|
#
|
37
41
|
# Raises an exception if no queue or class is given.
|
38
42
|
def self.create(queue, klass, *args)
|
39
|
-
if queue
|
43
|
+
if !queue
|
40
44
|
raise NoQueueError.new("Jobs must be placed onto a queue.")
|
41
45
|
end
|
42
46
|
|
@@ -47,6 +51,50 @@ module Resque
|
|
47
51
|
Resque.push(queue, :class => klass.to_s, :args => args)
|
48
52
|
end
|
49
53
|
|
54
|
+
# Removes a job from a queue. Expects a string queue name, a
|
55
|
+
# string class name, and, optionally, args.
|
56
|
+
#
|
57
|
+
# Returns the number of jobs destroyed.
|
58
|
+
#
|
59
|
+
# If no args are provided, it will remove all jobs of the class
|
60
|
+
# provided.
|
61
|
+
#
|
62
|
+
# That is, for these two jobs:
|
63
|
+
#
|
64
|
+
# { 'class' => 'UpdateGraph', 'args' => ['defunkt'] }
|
65
|
+
# { 'class' => 'UpdateGraph', 'args' => ['mojombo'] }
|
66
|
+
#
|
67
|
+
# The following call will remove both:
|
68
|
+
#
|
69
|
+
# Resque::Job.destroy(queue, 'UpdateGraph')
|
70
|
+
#
|
71
|
+
# Whereas specifying args will only remove the 2nd job:
|
72
|
+
#
|
73
|
+
# Resque::Job.destroy(queue, 'UpdateGraph', 'mojombo')
|
74
|
+
#
|
75
|
+
# This method can be potentially very slow and memory intensive,
|
76
|
+
# depending on the size of your queue, as it loads all jobs into
|
77
|
+
# a Ruby array before processing.
|
78
|
+
def self.destroy(queue, klass, *args)
|
79
|
+
klass = klass.to_s
|
80
|
+
|
81
|
+
destroyed = 0
|
82
|
+
|
83
|
+
mongo.find(:queue => queue).each do |rec|
|
84
|
+
json = decode(rec['item'])
|
85
|
+
|
86
|
+
match = json['class'] == klass
|
87
|
+
match &= json['args'] == args unless args.empty?
|
88
|
+
|
89
|
+
if match
|
90
|
+
destroyed += 1
|
91
|
+
mongo.remove(:_id => rec['_id'])
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
destroyed
|
96
|
+
end
|
97
|
+
|
50
98
|
# Given a string queue name, returns an instance of Resque::Job
|
51
99
|
# if any jobs are available. If not, returns nil.
|
52
100
|
def self.reserve(queue)
|
@@ -58,7 +106,64 @@ module Resque
|
|
58
106
|
# Calls #perform on the class given in the payload with the
|
59
107
|
# arguments given in the payload.
|
60
108
|
def perform
|
61
|
-
|
109
|
+
job = payload_class
|
110
|
+
job_args = args || []
|
111
|
+
job_was_performed = false
|
112
|
+
|
113
|
+
before_hooks = Plugin.before_hooks(job)
|
114
|
+
around_hooks = Plugin.around_hooks(job)
|
115
|
+
after_hooks = Plugin.after_hooks(job)
|
116
|
+
failure_hooks = Plugin.failure_hooks(job)
|
117
|
+
|
118
|
+
begin
|
119
|
+
# Execute before_perform hook. Abort the job gracefully if
|
120
|
+
# Resque::DontPerform is raised.
|
121
|
+
begin
|
122
|
+
before_hooks.each do |hook|
|
123
|
+
job.send(hook, *job_args)
|
124
|
+
end
|
125
|
+
rescue DontPerform
|
126
|
+
return false
|
127
|
+
end
|
128
|
+
|
129
|
+
# Execute the job. Do it in an around_perform hook if available.
|
130
|
+
if around_hooks.empty?
|
131
|
+
job.perform(*job_args)
|
132
|
+
job_was_performed = true
|
133
|
+
else
|
134
|
+
# We want to nest all around_perform plugins, with the last one
|
135
|
+
# finally calling perform
|
136
|
+
stack = around_hooks.reverse.inject(nil) do |last_hook, hook|
|
137
|
+
if last_hook
|
138
|
+
lambda do
|
139
|
+
job.send(hook, *job_args) { last_hook.call }
|
140
|
+
end
|
141
|
+
else
|
142
|
+
lambda do
|
143
|
+
job.send(hook, *job_args) do
|
144
|
+
job.perform(*job_args)
|
145
|
+
job_was_performed = true
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
stack.call
|
151
|
+
end
|
152
|
+
|
153
|
+
# Execute after_perform hook
|
154
|
+
after_hooks.each do |hook|
|
155
|
+
job.send(hook, *job_args)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Return true if the job was performed
|
159
|
+
return job_was_performed
|
160
|
+
|
161
|
+
# If an exception occurs during the job execution, look for an
|
162
|
+
# on_failure hook then re-raise.
|
163
|
+
rescue Object => e
|
164
|
+
failure_hooks.each { |hook| job.send(hook, e, *job_args) }
|
165
|
+
raise e
|
166
|
+
end
|
62
167
|
end
|
63
168
|
|
64
169
|
# Returns the actual class constant represented in this job's payload.
|