resque-mongo 1.4.0 → 1.8.1
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|