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.
- data/HISTORY.md +259 -0
- data/LICENSE +20 -0
- data/README.markdown +828 -0
- data/Rakefile +73 -0
- data/bin/resque +75 -0
- data/bin/resque-web +23 -0
- data/lib/resque/errors.rb +10 -0
- data/lib/resque/failure/base.rb +74 -0
- data/lib/resque/failure/hoptoad.rb +139 -0
- data/lib/resque/failure/mongo.rb +92 -0
- data/lib/resque/failure/multiple.rb +60 -0
- data/lib/resque/failure.rb +82 -0
- data/lib/resque/helpers.rb +79 -0
- data/lib/resque/job.rb +228 -0
- data/lib/resque/plugin.rb +51 -0
- data/lib/resque/queue_stats.rb +58 -0
- data/lib/resque/server/public/idle.png +0 -0
- data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
- data/lib/resque/server/public/jquery.relatize_date.js +95 -0
- data/lib/resque/server/public/poll.png +0 -0
- data/lib/resque/server/public/ranger.js +73 -0
- data/lib/resque/server/public/reset.css +48 -0
- data/lib/resque/server/public/style.css +86 -0
- data/lib/resque/server/public/working.png +0 -0
- data/lib/resque/server/test_helper.rb +19 -0
- data/lib/resque/server/views/error.erb +1 -0
- data/lib/resque/server/views/failed.erb +75 -0
- data/lib/resque/server/views/key_sets.erb +19 -0
- data/lib/resque/server/views/key_string.erb +11 -0
- data/lib/resque/server/views/layout.erb +38 -0
- data/lib/resque/server/views/next_more.erb +19 -0
- data/lib/resque/server/views/overview.erb +4 -0
- data/lib/resque/server/views/queues.erb +49 -0
- data/lib/resque/server/views/stats.erb +62 -0
- data/lib/resque/server/views/workers.erb +109 -0
- data/lib/resque/server/views/working.erb +68 -0
- data/lib/resque/server.rb +222 -0
- data/lib/resque/stat.rb +55 -0
- data/lib/resque/tasks.rb +42 -0
- data/lib/resque/version.rb +3 -0
- data/lib/resque/worker.rb +524 -0
- data/lib/resque.rb +384 -0
- data/tasks/redis.rake +161 -0
- data/tasks/resque.rake +2 -0
- data/test/dump.rdb +0 -0
- data/test/job_hooks_test.rb +323 -0
- data/test/job_plugins_test.rb +230 -0
- data/test/plugin_test.rb +116 -0
- data/test/queue_stats_test.rb +57 -0
- data/test/redis-test.conf +115 -0
- data/test/resque-web_test.rb +48 -0
- data/test/resque_test.rb +256 -0
- data/test/test_helper.rb +151 -0
- data/test/worker_test.rb +356 -0
- metadata +166 -0
data/Rakefile
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
load 'tasks/redis.rake'
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift 'lib'
|
4
|
+
require 'resque/tasks'
|
5
|
+
|
6
|
+
def command?(command)
|
7
|
+
system("type #{command} > /dev/null 2>&1")
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
#
|
12
|
+
# Tests
|
13
|
+
#
|
14
|
+
|
15
|
+
require 'rake/testtask'
|
16
|
+
|
17
|
+
task :default => :test
|
18
|
+
|
19
|
+
if command?(:rg)
|
20
|
+
desc "Run the test suite with rg"
|
21
|
+
task :test do
|
22
|
+
Dir['test/**/*_test.rb'].each do |f|
|
23
|
+
sh("rg #{f}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
else
|
27
|
+
Rake::TestTask.new do |test|
|
28
|
+
test.libs << "test"
|
29
|
+
test.test_files = FileList['test/**/*_test.rb']
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
if command? :kicker
|
34
|
+
desc "Launch Kicker (like autotest)"
|
35
|
+
task :kicker do
|
36
|
+
puts "Kicking... (ctrl+c to cancel)"
|
37
|
+
exec "kicker -e rake test lib examples"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Install
|
43
|
+
#
|
44
|
+
|
45
|
+
task :install => [ 'redis:install', 'dtach:install' ]
|
46
|
+
|
47
|
+
|
48
|
+
#
|
49
|
+
# Documentation
|
50
|
+
#
|
51
|
+
|
52
|
+
begin
|
53
|
+
require 'sdoc_helpers'
|
54
|
+
rescue LoadError
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
#
|
59
|
+
# Publishing
|
60
|
+
#
|
61
|
+
|
62
|
+
desc "Push a new version to Gemcutter"
|
63
|
+
task :publish do
|
64
|
+
require 'resque/version'
|
65
|
+
|
66
|
+
sh "gem build resque.gemspec"
|
67
|
+
sh "gem push resque-#{Resque::Version}.gem"
|
68
|
+
sh "git tag v#{Resque::Version}"
|
69
|
+
sh "git push origin v#{Resque::Version}"
|
70
|
+
sh "git push origin master"
|
71
|
+
sh "git clean -fd"
|
72
|
+
exec "rake pages"
|
73
|
+
end
|
data/bin/resque
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
require 'resque'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
parser = OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: resque [options] COMMAND"
|
9
|
+
|
10
|
+
opts.separator ""
|
11
|
+
opts.separator "Options:"
|
12
|
+
|
13
|
+
opts.on("-m", "--mongo [HOST:PORT]", "Redis connection string") do |host|
|
14
|
+
Resque.mongo = host
|
15
|
+
end
|
16
|
+
|
17
|
+
opts.on("-N", "--namespace [NAMESPACE]", "Redis namespace") do |namespace|
|
18
|
+
Resque.redis.namespace = namespace
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on("-h", "--help", "Show this message") do
|
22
|
+
puts opts
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.separator ""
|
27
|
+
opts.separator "Commands:"
|
28
|
+
opts.separator " remove WORKER Removes a worker"
|
29
|
+
opts.separator " kill WORKER Kills a worker"
|
30
|
+
opts.separator " list Lists known workers"
|
31
|
+
end
|
32
|
+
|
33
|
+
def kill(worker)
|
34
|
+
abort "** resque kill WORKER_ID" if worker.nil?
|
35
|
+
pid = worker.split(':')[1].to_i
|
36
|
+
|
37
|
+
begin
|
38
|
+
Process.kill("KILL", pid)
|
39
|
+
puts "** killed #{worker}"
|
40
|
+
rescue Errno::ESRCH
|
41
|
+
puts "** worker #{worker} not running"
|
42
|
+
end
|
43
|
+
|
44
|
+
remove worker
|
45
|
+
end
|
46
|
+
|
47
|
+
def remove(worker)
|
48
|
+
abort "** resque remove WORKER_ID" if worker.nil?
|
49
|
+
|
50
|
+
Resque.remove_worker(worker)
|
51
|
+
puts "** removed #{worker}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def list
|
55
|
+
if Resque.workers.any?
|
56
|
+
Resque.workers.each do |worker|
|
57
|
+
puts "#{worker} (#{worker.state})"
|
58
|
+
end
|
59
|
+
else
|
60
|
+
puts "None"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
parser.parse!
|
65
|
+
|
66
|
+
case ARGV[0]
|
67
|
+
when 'kill'
|
68
|
+
kill ARGV[1]
|
69
|
+
when 'remove'
|
70
|
+
remove ARGV[1]
|
71
|
+
when 'list'
|
72
|
+
list
|
73
|
+
else
|
74
|
+
puts parser.help
|
75
|
+
end
|
data/bin/resque-web
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
begin
|
5
|
+
require 'vegas'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'vegas'
|
9
|
+
end
|
10
|
+
require 'resque/server'
|
11
|
+
|
12
|
+
|
13
|
+
Vegas::Runner.new(Resque::Server, 'resque-web', {
|
14
|
+
:before_run => lambda {|v|
|
15
|
+
path = (ENV['RESQUECONFIG'] || v.args.first)
|
16
|
+
load path.to_s.strip if path
|
17
|
+
}
|
18
|
+
}) do |runner, opts, app|
|
19
|
+
opts.on('-N NAMESPACE', "--namespace NAMESPACE", "set the Redis namespace") {|namespace|
|
20
|
+
runner.logger.info "Using Redis namespace '#{namespace}'"
|
21
|
+
Resque.redis.namespace = namespace
|
22
|
+
}
|
23
|
+
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,74 @@
|
|
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
|
+
# The number of failures after a search.
|
40
|
+
def self.search_count
|
41
|
+
0
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns a paginated array of failure objects.
|
45
|
+
def self.all(start = 0, count = 1)
|
46
|
+
[]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns a paginated array of failure objects after a search.
|
50
|
+
def self.search_results(query, start = 0, count = 1)
|
51
|
+
[]
|
52
|
+
end
|
53
|
+
|
54
|
+
# A URL where someone can go to view failures.
|
55
|
+
def self.url
|
56
|
+
end
|
57
|
+
|
58
|
+
# Clear all failure objects
|
59
|
+
def self.clear
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.requeue(index)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.remove(index)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Logging!
|
69
|
+
def log(message)
|
70
|
+
@worker.log(message)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'builder'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
module Resque
|
6
|
+
module Failure
|
7
|
+
# A Failure backend that sends exceptions raised by jobs to Hoptoad.
|
8
|
+
#
|
9
|
+
# To use it, put this code in an initializer, Rake task, or wherever:
|
10
|
+
#
|
11
|
+
# require 'resque/failure/hoptoad'
|
12
|
+
#
|
13
|
+
# Resque::Failure::Hoptoad.configure do |config|
|
14
|
+
# config.api_key = 'blah'
|
15
|
+
# config.secure = true
|
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"
|
23
|
+
# end
|
24
|
+
class Hoptoad < Base
|
25
|
+
# From the hoptoad plugin
|
26
|
+
INPUT_FORMAT = /^([^:]+):(\d+)(?::in `([^']+)')?$/
|
27
|
+
|
28
|
+
class << self
|
29
|
+
attr_accessor :secure, :api_key
|
30
|
+
attr_accessor :proxy_host, :proxy_port, :proxy_user, :proxy_pass
|
31
|
+
attr_accessor :server_environment
|
32
|
+
attr_accessor :host, :port
|
33
|
+
attr_accessor :http_read_timeout, :http_open_timeout
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.count
|
37
|
+
# We can't get the total # of errors from Hoptoad so we fake it
|
38
|
+
# by asking Resque how many errors it has seen.
|
39
|
+
Stat[:failed]
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.configure
|
43
|
+
yield self
|
44
|
+
Resque::Failure.backend = self
|
45
|
+
end
|
46
|
+
|
47
|
+
def save
|
48
|
+
http = use_ssl? ? :https : :http
|
49
|
+
host = self.class.host || 'hoptoadapp.com'
|
50
|
+
port = self.class.port
|
51
|
+
url = URI.parse("#{http}://#{host}:#{port}/notifier_api/v2/notices/")
|
52
|
+
|
53
|
+
request = Net::HTTP::Proxy self.class.proxy_host, self.class.proxy_port,
|
54
|
+
self.class.proxy_user, self.class.proxy_pass
|
55
|
+
http = request.new(url.host, url.port)
|
56
|
+
headers = {
|
57
|
+
'Content-type' => 'text/xml',
|
58
|
+
'Accept' => 'text/xml, application/xml'
|
59
|
+
}
|
60
|
+
|
61
|
+
http.read_timeout = self.class.http_read_timeout || 5 # seconds
|
62
|
+
http.open_timeout = self.class.http_open_timeout || 2 # seconds
|
63
|
+
|
64
|
+
http.use_ssl = use_ssl?
|
65
|
+
|
66
|
+
begin
|
67
|
+
response = http.post(url.path, xml, headers)
|
68
|
+
rescue TimeoutError => e
|
69
|
+
log "Timeout while contacting the Hoptoad server."
|
70
|
+
end
|
71
|
+
|
72
|
+
case response
|
73
|
+
when Net::HTTPSuccess then
|
74
|
+
log "Hoptoad Success: #{response.class}"
|
75
|
+
else
|
76
|
+
body = response.body if response.respond_to? :body
|
77
|
+
log "Hoptoad Failure: #{response.class}\n#{body}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def xml
|
82
|
+
x = Builder::XmlMarkup.new
|
83
|
+
x.instruct!
|
84
|
+
x.notice :version=>"2.0" do
|
85
|
+
x.tag! "api-key", api_key
|
86
|
+
x.notifier do
|
87
|
+
x.name "Resqueue"
|
88
|
+
x.version "0.1"
|
89
|
+
x.url "http://github.com/defunkt/resque"
|
90
|
+
end
|
91
|
+
x.error do
|
92
|
+
x.tag! "class", exception.class.name
|
93
|
+
x.message "#{exception.class.name}: #{exception.message}"
|
94
|
+
x.backtrace do
|
95
|
+
fill_in_backtrace_lines(x)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
x.request do
|
99
|
+
x.url queue.to_s
|
100
|
+
x.component worker.to_s
|
101
|
+
x.params do
|
102
|
+
x.var :key=>"payload_class" do
|
103
|
+
x.text! payload["class"].to_s
|
104
|
+
end
|
105
|
+
x.var :key=>"payload_args" do
|
106
|
+
x.text! payload["args"].to_s
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
x.tag!("server-environment") do
|
111
|
+
x.tag!("environment-name",server_environment)
|
112
|
+
x.tag!("project-root", "RAILS_ROOT")
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def fill_in_backtrace_lines(x)
|
119
|
+
Array(exception.backtrace).each do |unparsed_line|
|
120
|
+
_, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
|
121
|
+
x.line :file => file,:number => number
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def use_ssl?
|
126
|
+
self.class.secure
|
127
|
+
end
|
128
|
+
|
129
|
+
def api_key
|
130
|
+
self.class.api_key
|
131
|
+
end
|
132
|
+
|
133
|
+
def server_environment
|
134
|
+
return self.class.server_environment if self.class.server_environment
|
135
|
+
defined?(RAILS_ENV) ? RAILS_ENV : (ENV['RACK_ENV'] || 'development')
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Resque
|
4
|
+
module Failure
|
5
|
+
# A Failure backend that stores exceptions in Mongo. Very simple but
|
6
|
+
# works out of the box, along with support in the Resque web app.
|
7
|
+
class Mongo < Base
|
8
|
+
def save
|
9
|
+
data = {
|
10
|
+
:failed_at => Time.now.utc,
|
11
|
+
:payload => payload,
|
12
|
+
:exception => exception.class.to_s,
|
13
|
+
:error => exception.to_s,
|
14
|
+
:backtrace => Array(exception.backtrace),
|
15
|
+
:worker => worker.to_s,
|
16
|
+
:queue => queue
|
17
|
+
}
|
18
|
+
Resque.mongo_failures << data
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.count
|
22
|
+
Resque.mongo_failures.count
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.search_count
|
26
|
+
@@search_results.count
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.all(start = 0, count = 1)
|
30
|
+
start, count = [start, count].map { |n| Integer(n) }
|
31
|
+
all_failures = Resque.mongo_failures.find().sort([:failed_at, :desc]).skip(start).limit(count).to_a
|
32
|
+
all_failures.size == 1 ? all_failures.first : all_failures
|
33
|
+
end
|
34
|
+
|
35
|
+
# Looks for failed jobs who match a particular search string
|
36
|
+
def self.search_results(query, start = 0, count = 1)
|
37
|
+
# If the search query is nil or empty, return an empty array
|
38
|
+
if query.nil? || query.empty?
|
39
|
+
@@search_results = []
|
40
|
+
return []
|
41
|
+
end
|
42
|
+
|
43
|
+
start, count = [start, count].map { |n| Integer(n) }
|
44
|
+
set_results = Set.new
|
45
|
+
|
46
|
+
# For each search term, retrieve the failed jobs that contain at least one relevant field matching the regexp defined by that search term
|
47
|
+
query.split.each do |term|
|
48
|
+
partial_results = Resque.mongo_failures.find(
|
49
|
+
{"$or" => [
|
50
|
+
{"exception" => /#{term}/i},
|
51
|
+
{"error" => /#{term}/i},
|
52
|
+
{"payload.class" => /#{term}/i},
|
53
|
+
{"payload.args" => /#{term}/i},
|
54
|
+
{"worker" => /#{term}/i},
|
55
|
+
{"queue" => /#{term}/i},
|
56
|
+
{"backtrace" => /#{term}/i}
|
57
|
+
] }
|
58
|
+
).sort([:failed_at, :desc]).to_a
|
59
|
+
|
60
|
+
# If the set was empty, merge the first results, else intersect it with the current results
|
61
|
+
if set_results.empty?
|
62
|
+
set_results.merge(partial_results)
|
63
|
+
else
|
64
|
+
set_results = set_results & partial_results
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# search_res will be an array containing 'count' values, starting with 'start', sorted in descending order
|
69
|
+
@@search_results = set_results.to_a || []
|
70
|
+
search_results = set_results.to_a[start, count]
|
71
|
+
|
72
|
+
search_results || []
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.clear
|
76
|
+
Resque.mongo_failures.remove
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.requeue(index)
|
80
|
+
item = all(index)
|
81
|
+
item['retried_at'] = Time.now.strftime("%Y/%m/%d %H:%M:%S")
|
82
|
+
Resque.mongo_failures.update({:_id => item['_id']}, item)
|
83
|
+
Job.create(item['queue'], item['payload']['class'], *item['payload']['args'])
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.remove(index)
|
87
|
+
item = all(index)
|
88
|
+
Resque.mongo_failures.remove(:_id => item['_id'])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|