grockit-resque 1.5.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 (61) hide show
  1. data/.gitignore +2 -0
  2. data/.kick +26 -0
  3. data/CONTRIBUTORS +14 -0
  4. data/HISTORY.md +78 -0
  5. data/LICENSE +20 -0
  6. data/README.markdown +762 -0
  7. data/Rakefile +67 -0
  8. data/bin/resque +57 -0
  9. data/bin/resque-web +18 -0
  10. data/config.ru +14 -0
  11. data/deps.rip +6 -0
  12. data/examples/async_helper.rb +31 -0
  13. data/examples/demo/README.markdown +71 -0
  14. data/examples/demo/Rakefile +3 -0
  15. data/examples/demo/app.rb +38 -0
  16. data/examples/demo/config.ru +19 -0
  17. data/examples/demo/job.rb +22 -0
  18. data/examples/god/resque.god +53 -0
  19. data/examples/god/stale.god +26 -0
  20. data/examples/instance.rb +11 -0
  21. data/examples/simple.rb +30 -0
  22. data/init.rb +1 -0
  23. data/lib/resque.rb +247 -0
  24. data/lib/resque/errors.rb +7 -0
  25. data/lib/resque/failure.rb +63 -0
  26. data/lib/resque/failure/base.rb +58 -0
  27. data/lib/resque/failure/hoptoad.rb +122 -0
  28. data/lib/resque/failure/multiple.rb +44 -0
  29. data/lib/resque/failure/redis.rb +33 -0
  30. data/lib/resque/helpers.rb +57 -0
  31. data/lib/resque/job.rb +158 -0
  32. data/lib/resque/server.rb +182 -0
  33. data/lib/resque/server/public/idle.png +0 -0
  34. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  35. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  36. data/lib/resque/server/public/poll.png +0 -0
  37. data/lib/resque/server/public/ranger.js +24 -0
  38. data/lib/resque/server/public/reset.css +48 -0
  39. data/lib/resque/server/public/style.css +75 -0
  40. data/lib/resque/server/public/working.png +0 -0
  41. data/lib/resque/server/views/error.erb +1 -0
  42. data/lib/resque/server/views/failed.erb +35 -0
  43. data/lib/resque/server/views/key.erb +17 -0
  44. data/lib/resque/server/views/layout.erb +38 -0
  45. data/lib/resque/server/views/next_more.erb +10 -0
  46. data/lib/resque/server/views/overview.erb +4 -0
  47. data/lib/resque/server/views/queues.erb +46 -0
  48. data/lib/resque/server/views/stats.erb +62 -0
  49. data/lib/resque/server/views/workers.erb +78 -0
  50. data/lib/resque/server/views/working.erb +69 -0
  51. data/lib/resque/stat.rb +53 -0
  52. data/lib/resque/tasks.rb +39 -0
  53. data/lib/resque/version.rb +3 -0
  54. data/lib/resque/worker.rb +442 -0
  55. data/tasks/redis.rake +135 -0
  56. data/tasks/resque.rake +2 -0
  57. data/test/redis-test.conf +132 -0
  58. data/test/resque_test.rb +227 -0
  59. data/test/test_helper.rb +96 -0
  60. data/test/worker_test.rb +243 -0
  61. metadata +172 -0
@@ -0,0 +1,30 @@
1
+ # This is a simple Resque job.
2
+ class Archive
3
+ @queue = :file_serve
4
+
5
+ def self.perform(repo_id, branch = 'master')
6
+ repo = Repository.find(repo_id)
7
+ repo.create_archive(branch)
8
+ end
9
+ end
10
+
11
+ # This is in our app code
12
+ class Repository < Model
13
+ # ... stuff ...
14
+
15
+ def async_create_archive(branch)
16
+ Resque.enqueue(Archive, self.id, branch)
17
+ end
18
+
19
+ # ... more stuff ...
20
+ end
21
+
22
+ # Calling this code:
23
+ repo = Repository.find(22)
24
+ repo.async_create_archive('homebrew')
25
+
26
+ # Will return immediately and create a Resque job which is later
27
+ # processed.
28
+
29
+ # Essentially, this code is run by the worker when processing:
30
+ Archive.perform(22, 'homebrew')
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'resque'
@@ -0,0 +1,247 @@
1
+ require 'redis/namespace'
2
+
3
+ begin
4
+ require 'yajl'
5
+ rescue LoadError
6
+ require 'json'
7
+ end
8
+
9
+ require 'resque/version'
10
+
11
+ require 'resque/errors'
12
+
13
+ require 'resque/failure'
14
+ require 'resque/failure/base'
15
+
16
+ require 'resque/helpers'
17
+ require 'resque/stat'
18
+ require 'resque/job'
19
+ require 'resque/worker'
20
+
21
+ module Resque
22
+ include Helpers
23
+ extend self
24
+
25
+ # Accepts:
26
+ # 1. A 'hostname:port' string
27
+ # 2. A 'hostname:port:db' string (to select the Redis db)
28
+ # 3. An instance of `Redis`
29
+ def redis=(server)
30
+ case server
31
+ when String
32
+ host, port, db = server.split(':')
33
+ redis = Redis.new(:host => host, :port => port,
34
+ :thread_safe => true, :db => db)
35
+ @redis = Redis::Namespace.new(:resque, :redis => redis)
36
+ when Redis
37
+ @redis = Redis::Namespace.new(:resque, :redis => server)
38
+ else
39
+ raise "I don't know what to do with #{server.inspect}"
40
+ end
41
+ end
42
+
43
+ # Returns the current Redis connection. If none has been created, will
44
+ # create a new one.
45
+ def redis
46
+ return @redis if @redis
47
+ self.redis = 'localhost:6379'
48
+ self.redis
49
+ end
50
+
51
+ def to_s
52
+ "Resque Client connected to #{redis.server}"
53
+ end
54
+
55
+
56
+ #
57
+ # queue manipulation
58
+ #
59
+
60
+ # Pushes a job onto a queue. Queue name should be a string and the
61
+ # item should be any JSON-able Ruby object.
62
+ def push(queue, item)
63
+ watch_queue(queue)
64
+ queue_key = "queue:#{queue}"
65
+ if item[:unique]
66
+ unless redis.lrange(queue_key, 0, -1).include?(encode(item))
67
+ redis.rpush queue_key, encode(item)
68
+ end
69
+ else
70
+ redis.rpush queue_key, encode(item)
71
+ end
72
+ end
73
+
74
+ # Pops a job off a queue. Queue name should be a string.
75
+ #
76
+ # Returns a Ruby object.
77
+ def pop(queue)
78
+ decode redis.lpop("queue:#{queue}")
79
+ end
80
+
81
+ # Returns an int representing the size of a queue.
82
+ # Queue name should be a string.
83
+ def size(queue)
84
+ redis.llen("queue:#{queue}").to_i
85
+ end
86
+
87
+ # Returns an array of items currently queued. Queue name should be
88
+ # a string.
89
+ #
90
+ # start and count should be integer and can be used for pagination.
91
+ # start is the item to begin, count is how many items to return.
92
+ #
93
+ # To get the 3rd page of a 30 item, paginatied list one would use:
94
+ # Resque.peek('my_list', 59, 30)
95
+ def peek(queue, start = 0, count = 1)
96
+ list_range("queue:#{queue}", start, count)
97
+ end
98
+
99
+ # Does the dirty work of fetching a range of items from a Redis list
100
+ # and converting them into Ruby objects.
101
+ def list_range(key, start = 0, count = 1)
102
+ if count == 1
103
+ decode redis.lindex(key, start)
104
+ else
105
+ Array(redis.lrange(key, start, start+count-1)).map do |item|
106
+ decode item
107
+ end
108
+ end
109
+ end
110
+
111
+ # Returns an array of all known Resque queues as strings.
112
+ def queues
113
+ redis.smembers(:queues)
114
+ end
115
+
116
+ # Given a queue name, completely deletes the queue.
117
+ def remove_queue(queue)
118
+ redis.srem(:queues, queue.to_s)
119
+ redis.del("queue:#{queue}")
120
+ end
121
+
122
+ # Used internally to keep track of which queues we've created.
123
+ # Don't call this directly.
124
+ def watch_queue(queue)
125
+ redis.sadd(:queues, queue.to_s)
126
+ end
127
+
128
+
129
+ #
130
+ # job shortcuts
131
+ #
132
+
133
+ # This method can be used to conveniently add a job to a queue.
134
+ # It assumes the class you're passing it is a real Ruby class (not
135
+ # a string or reference) which either:
136
+ #
137
+ # a) has a @queue ivar set
138
+ # b) responds to `queue`
139
+ #
140
+ # If either of those conditions are met, it will use the value obtained
141
+ # from performing one of the above operations to determine the queue.
142
+ #
143
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
144
+ #
145
+ # This method is considered part of the `stable` API.
146
+ def enqueue(klass, *args)
147
+ Job.create(queue_from_class(klass), klass, *args)
148
+ end
149
+
150
+ def enqueue_unique(klass, *args)
151
+ Job.create_unique(queue_from_class(klass), klass, *args)
152
+ end
153
+
154
+ # This method can be used to conveniently remove a job from a queue.
155
+ # It assumes the class you're passing it is a real Ruby class (not
156
+ # a string or reference) which either:
157
+ #
158
+ # a) has a @queue ivar set
159
+ # b) responds to `queue`
160
+ #
161
+ # If either of those conditions are met, it will use the value obtained
162
+ # from performing one of the above operations to determine the queue.
163
+ #
164
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
165
+ #
166
+ # If no args are given, this method will dequeue *all* jobs matching
167
+ # the provided class. See `Resque::Job.destroy` for more
168
+ # information.
169
+ #
170
+ # Returns the number of jobs destroyed.
171
+ #
172
+ # Example:
173
+ #
174
+ # # Removes all jobs of class `UpdateNetworkGraph`
175
+ # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph)
176
+ #
177
+ # # Removes all jobs of class `UpdateNetworkGraph` with matching args.
178
+ # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph, 'repo:135325')
179
+ #
180
+ # This method is considered part of the `stable` API.
181
+ def dequeue(klass, *args)
182
+ Job.destroy(queue_from_class(klass), klass, *args)
183
+ end
184
+
185
+ # Given a class, try to extrapolate an appropriate queue based on a
186
+ # class instance variable or `queue` method.
187
+ def queue_from_class(klass)
188
+ klass.instance_variable_get(:@queue) ||
189
+ (klass.respond_to?(:queue) and klass.queue)
190
+ end
191
+
192
+ # This method will return a `Resque::Job` object or a non-true value
193
+ # depending on whether a job can be obtained. You should pass it the
194
+ # precise name of a queue: case matters.
195
+ #
196
+ # This method is considered part of the `stable` API.
197
+ def reserve(queue)
198
+ Job.reserve(queue)
199
+ end
200
+
201
+
202
+ #
203
+ # worker shortcuts
204
+ #
205
+
206
+ # A shortcut to Worker.all
207
+ def workers
208
+ Worker.all
209
+ end
210
+
211
+ # A shortcut to Worker.working
212
+ def working
213
+ Worker.working
214
+ end
215
+
216
+ # A shortcut to unregister_worker
217
+ # useful for command line tool
218
+ def remove_worker(worker_id)
219
+ worker = Resque::Worker.find(worker_id)
220
+ worker.unregister_worker
221
+ end
222
+
223
+ #
224
+ # stats
225
+ #
226
+
227
+ # Returns a hash, similar to redis-rb's #info, of interesting stats.
228
+ def info
229
+ return {
230
+ :pending => queues.inject(0) { |m,k| m + size(k) },
231
+ :processed => Stat[:processed],
232
+ :queues => queues.size,
233
+ :workers => workers.size.to_i,
234
+ :working => working.size,
235
+ :failed => Stat[:failed],
236
+ :servers => [redis.server]
237
+ }
238
+ end
239
+
240
+ # Returns an array of all known Resque keys in Redis. Redis' KEYS operation
241
+ # is O(N) for the keyspace, so be careful - this can be slow for big databases.
242
+ def keys
243
+ redis.keys("*").map do |key|
244
+ key.sub('resque:', '')
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,7 @@
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
+ end
@@ -0,0 +1,63 @@
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/redis'
36
+ @backend = Failure::Redis
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 an array of all the failures, paginated.
45
+ #
46
+ # `start` is the int of the first item in the page, `count` is the
47
+ # number of items to return.
48
+ def self.all(start = 0, count = 1)
49
+ backend.all(start, count)
50
+ end
51
+
52
+ # The string url of the backend's web interface, if any.
53
+ def self.url
54
+ backend.url
55
+ end
56
+
57
+ # Clear all failure jobs
58
+ def self.clear
59
+ backend.clear
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,58 @@
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
+ # Returns a paginated array of failure objects.
40
+ def self.all(start = 0, count = 1)
41
+ []
42
+ end
43
+
44
+ # A URL where someone can go to view failures.
45
+ def self.url
46
+ end
47
+
48
+ # Clear all failure objects
49
+ def self.clear
50
+ end
51
+
52
+ # Logging!
53
+ def log(message)
54
+ @worker.log(message)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,122 @@
1
+ require 'net/http'
2
+ require 'builder'
3
+
4
+ module Resque
5
+ module Failure
6
+ # A Failure backend that sends exceptions raised by jobs to Hoptoad.
7
+ #
8
+ # To use it, put this code in an initializer, Rake task, or wherever:
9
+ #
10
+ # Resque::Failure::Hoptoad.configure do |config|
11
+ # config.api_key = 'blah'
12
+ # config.secure = true
13
+ # config.subdomain = 'your_hoptoad_subdomain'
14
+ # end
15
+ class Hoptoad < Base
16
+ #from the hoptoad plugin
17
+ INPUT_FORMAT = %r{^([^:]+):(\d+)(?::in `([^']+)')?$}.freeze
18
+
19
+ class << self
20
+ attr_accessor :secure, :api_key, :subdomain
21
+ end
22
+
23
+ def self.url
24
+ "http://#{subdomain}.hoptoadapp.com/" if subdomain
25
+ end
26
+
27
+ def self.count
28
+ # We can't get the total # of errors from Hoptoad so we fake it
29
+ # by asking Resque how many errors it has seen.
30
+ Stat[:failed]
31
+ end
32
+
33
+ def self.configure
34
+ yield self
35
+ Resque::Failure.backend = self
36
+ end
37
+
38
+
39
+
40
+ def save
41
+ http = use_ssl? ? :https : :http
42
+ url = URI.parse("#{http}://hoptoadapp.com/notifier_api/v2/notices")
43
+
44
+ http = Net::HTTP.new(url.host, url.port)
45
+ headers = {
46
+ 'Content-type' => 'text/xml',
47
+ 'Accept' => 'text/xml, application/xml'
48
+ }
49
+
50
+ http.read_timeout = 5 # seconds
51
+ http.open_timeout = 2 # seconds
52
+
53
+ http.use_ssl = use_ssl?
54
+
55
+ begin
56
+ response = http.post(url.path, xml, headers)
57
+ rescue TimeoutError => e
58
+ log "Timeout while contacting the Hoptoad server."
59
+ end
60
+
61
+ case response
62
+ when Net::HTTPSuccess then
63
+ log "Hoptoad Success: #{response.class}"
64
+ else
65
+ body = response.body if response.respond_to? :body
66
+ log "Hoptoad Failure: #{response.class}\n#{body}"
67
+ end
68
+ end
69
+
70
+ def xml
71
+ x = Builder::XmlMarkup.new
72
+ x.instruct!
73
+ x.notice :version=>"2.0" do
74
+ x.tag! "api-key", api_key
75
+ x.notifier do
76
+ x.name "Resqueue"
77
+ x.version "0.1"
78
+ x.url "http://github.com/defunkt/resque"
79
+ end
80
+ x.error do
81
+ x.class exception.class.name
82
+ x.message "#{exception.class.name}: #{exception.message}"
83
+ x.backtrace do
84
+ fill_in_backtrace_lines(x)
85
+ end
86
+ end
87
+ x.request do
88
+ x.url queue.to_s
89
+ x.component worker.to_s
90
+ x.params do
91
+ x.var :key=>"payload_class" do
92
+ x.text! payload["class"].to_s
93
+ end
94
+ x.var :key=>"payload_args" do
95
+ x.text! payload["args"].to_s
96
+ end
97
+ end
98
+ end
99
+ x.tag!("server-environment") do
100
+ x.tag!("environment-name",RAILS_ENV)
101
+ end
102
+
103
+ end
104
+ end
105
+
106
+ def fill_in_backtrace_lines(x)
107
+ exception.backtrace.each do |unparsed_line|
108
+ _, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
109
+ x.line :file=>file,:number=>number
110
+ end
111
+ end
112
+
113
+ def use_ssl?
114
+ self.class.secure
115
+ end
116
+
117
+ def api_key
118
+ self.class.api_key
119
+ end
120
+ end
121
+ end
122
+ end