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
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