tr_resque 1.20.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.
Files changed (58) hide show
  1. data/HISTORY.md +354 -0
  2. data/LICENSE +20 -0
  3. data/README.markdown +908 -0
  4. data/Rakefile +70 -0
  5. data/bin/resque +81 -0
  6. data/bin/resque-web +27 -0
  7. data/lib/resque.rb +369 -0
  8. data/lib/resque/errors.rb +10 -0
  9. data/lib/resque/failure.rb +96 -0
  10. data/lib/resque/failure/airbrake.rb +17 -0
  11. data/lib/resque/failure/base.rb +64 -0
  12. data/lib/resque/failure/hoptoad.rb +33 -0
  13. data/lib/resque/failure/multiple.rb +54 -0
  14. data/lib/resque/failure/redis.rb +51 -0
  15. data/lib/resque/failure/thoughtbot.rb +33 -0
  16. data/lib/resque/helpers.rb +94 -0
  17. data/lib/resque/job.rb +227 -0
  18. data/lib/resque/plugin.rb +66 -0
  19. data/lib/resque/server.rb +248 -0
  20. data/lib/resque/server/public/favicon.ico +0 -0
  21. data/lib/resque/server/public/idle.png +0 -0
  22. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  23. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  24. data/lib/resque/server/public/poll.png +0 -0
  25. data/lib/resque/server/public/ranger.js +73 -0
  26. data/lib/resque/server/public/reset.css +44 -0
  27. data/lib/resque/server/public/style.css +86 -0
  28. data/lib/resque/server/public/working.png +0 -0
  29. data/lib/resque/server/test_helper.rb +19 -0
  30. data/lib/resque/server/views/error.erb +1 -0
  31. data/lib/resque/server/views/failed.erb +67 -0
  32. data/lib/resque/server/views/key_sets.erb +19 -0
  33. data/lib/resque/server/views/key_string.erb +11 -0
  34. data/lib/resque/server/views/layout.erb +44 -0
  35. data/lib/resque/server/views/next_more.erb +10 -0
  36. data/lib/resque/server/views/overview.erb +4 -0
  37. data/lib/resque/server/views/queues.erb +49 -0
  38. data/lib/resque/server/views/stats.erb +62 -0
  39. data/lib/resque/server/views/workers.erb +109 -0
  40. data/lib/resque/server/views/working.erb +72 -0
  41. data/lib/resque/stat.rb +53 -0
  42. data/lib/resque/tasks.rb +61 -0
  43. data/lib/resque/version.rb +3 -0
  44. data/lib/resque/worker.rb +546 -0
  45. data/lib/tasks/redis.rake +161 -0
  46. data/lib/tasks/resque.rake +2 -0
  47. data/test/airbrake_test.rb +27 -0
  48. data/test/hoptoad_test.rb +26 -0
  49. data/test/job_hooks_test.rb +423 -0
  50. data/test/job_plugins_test.rb +230 -0
  51. data/test/plugin_test.rb +116 -0
  52. data/test/redis-test-cluster.conf +115 -0
  53. data/test/redis-test.conf +115 -0
  54. data/test/resque-web_test.rb +59 -0
  55. data/test/resque_test.rb +278 -0
  56. data/test/test_helper.rb +160 -0
  57. data/test/worker_test.rb +434 -0
  58. metadata +186 -0
data/Rakefile ADDED
@@ -0,0 +1,70 @@
1
+ #
2
+ # Setup
3
+ #
4
+
5
+ load 'lib/tasks/redis.rake'
6
+
7
+ $LOAD_PATH.unshift 'lib'
8
+ require 'resque/tasks'
9
+
10
+ def command?(command)
11
+ system("type #{command} > /dev/null 2>&1")
12
+ end
13
+
14
+
15
+ #
16
+ # Tests
17
+ #
18
+
19
+ require 'rake/testtask'
20
+
21
+ task :default => :test
22
+
23
+ Rake::TestTask.new do |test|
24
+ test.verbose = true
25
+ test.libs << "test"
26
+ test.libs << "lib"
27
+ test.test_files = FileList['test/**/*_test.rb']
28
+ end
29
+
30
+ if command? :kicker
31
+ desc "Launch Kicker (like autotest)"
32
+ task :kicker do
33
+ puts "Kicking... (ctrl+c to cancel)"
34
+ exec "kicker -e rake test lib examples"
35
+ end
36
+ end
37
+
38
+
39
+ #
40
+ # Install
41
+ #
42
+
43
+ task :install => [ 'redis:install', 'dtach:install' ]
44
+
45
+
46
+ #
47
+ # Documentation
48
+ #
49
+
50
+ begin
51
+ require 'sdoc_helpers'
52
+ rescue LoadError
53
+ end
54
+
55
+
56
+ #
57
+ # Publishing
58
+ #
59
+
60
+ desc "Push a new version to Gemcutter"
61
+ task :publish do
62
+ require 'resque/version'
63
+
64
+ sh "gem build resque.gemspec"
65
+ sh "gem push resque-#{Resque::Version}.gem"
66
+ sh "git tag v#{Resque::Version}"
67
+ sh "git push origin v#{Resque::Version}"
68
+ sh "git push origin master"
69
+ sh "git clean -fd"
70
+ end
data/bin/resque ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ begin
5
+ require 'redis-namespace'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'redis-namespace'
9
+ end
10
+ require 'resque'
11
+ require 'optparse'
12
+
13
+ parser = OptionParser.new do |opts|
14
+ opts.banner = "Usage: resque [options] COMMAND"
15
+
16
+ opts.separator ""
17
+ opts.separator "Options:"
18
+
19
+ opts.on("-r", "--redis [HOST:PORT]", "Redis connection string") do |host|
20
+ Resque.redis = host
21
+ end
22
+
23
+ opts.on("-N", "--namespace [NAMESPACE]", "Redis namespace") do |namespace|
24
+ Resque.redis.namespace = namespace
25
+ end
26
+
27
+ opts.on("-h", "--help", "Show this message") do
28
+ puts opts
29
+ exit
30
+ end
31
+
32
+ opts.separator ""
33
+ opts.separator "Commands:"
34
+ opts.separator " remove WORKER Removes a worker"
35
+ opts.separator " kill WORKER Kills a worker"
36
+ opts.separator " list Lists known workers"
37
+ end
38
+
39
+ def kill(worker)
40
+ abort "** resque kill WORKER_ID" if worker.nil?
41
+ pid = worker.split(':')[1].to_i
42
+
43
+ begin
44
+ Process.kill("KILL", pid)
45
+ puts "** killed #{worker}"
46
+ rescue Errno::ESRCH
47
+ puts "** worker #{worker} not running"
48
+ end
49
+
50
+ remove worker
51
+ end
52
+
53
+ def remove(worker)
54
+ abort "** resque remove WORKER_ID" if worker.nil?
55
+
56
+ Resque.remove_worker(worker)
57
+ puts "** removed #{worker}"
58
+ end
59
+
60
+ def list
61
+ if Resque.workers.any?
62
+ Resque.workers.each do |worker|
63
+ puts "#{worker} (#{worker.state})"
64
+ end
65
+ else
66
+ puts "None"
67
+ end
68
+ end
69
+
70
+ parser.parse!
71
+
72
+ case ARGV[0]
73
+ when 'kill'
74
+ kill ARGV[1]
75
+ when 'remove'
76
+ remove ARGV[1]
77
+ when 'list'
78
+ list
79
+ else
80
+ puts parser.help
81
+ end
data/bin/resque-web ADDED
@@ -0,0 +1,27 @@
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
+ opts.on('-r redis-connection', "--redis redis-connection", "set the Redis connection string") {|redis_conf|
24
+ runner.logger.info "Using Redis connection '#{redis_conf}'"
25
+ Resque.redis = redis_conf
26
+ }
27
+ end
data/lib/resque.rb ADDED
@@ -0,0 +1,369 @@
1
+ require 'redis/namespace'
2
+
3
+ require 'resque/version'
4
+
5
+ require 'resque/errors'
6
+
7
+ require 'resque/failure'
8
+ require 'resque/failure/base'
9
+
10
+ require 'resque/helpers'
11
+ require 'resque/stat'
12
+ require 'resque/job'
13
+ require 'resque/worker'
14
+ require 'resque/plugin'
15
+
16
+ module Resque
17
+ include Helpers
18
+ extend self
19
+
20
+ # Accepts:
21
+ # 1. A 'hostname:port' String
22
+ # 2. A 'hostname:port:db' String (to select the Redis db)
23
+ # 3. A 'hostname:port/namespace' String (to set the Redis namespace)
24
+ # 4. A Redis URL String 'redis://host:port'
25
+ # 5. An instance of `Redis`, `Redis::Client`, `Redis::DistRedis`,
26
+ # or `Redis::Namespace`.
27
+ def redis=(server)
28
+ case server
29
+ when String
30
+ if server =~ /redis\:\/\//
31
+ redis = Redis.connect(:url => server, :thread_safe => true)
32
+ else
33
+ server, namespace = server.split('/', 2)
34
+ host, port, db = server.split(':')
35
+ redis = Redis.new(:host => host, :port => port,
36
+ :thread_safe => true, :db => db)
37
+ end
38
+ namespace ||= :resque
39
+
40
+ @redis = Redis::Namespace.new(namespace, :redis => redis)
41
+ when Redis::Namespace
42
+ @redis = server
43
+ else
44
+ @redis = Redis::Namespace.new(:resque, :redis => server)
45
+ end
46
+ end
47
+
48
+ # Returns the current Redis connection. If none has been created, will
49
+ # create a new one.
50
+ def redis
51
+ return @redis if @redis
52
+ self.redis = Redis.respond_to?(:connect) ? Redis.connect : "localhost:6379"
53
+ self.redis
54
+ end
55
+
56
+ def redis_id
57
+ # support 1.x versions of redis-rb
58
+ if redis.respond_to?(:server)
59
+ redis.server
60
+ elsif redis.respond_to?(:nodes) # distributed
61
+ redis.nodes.map { |n| n.id }.join(', ')
62
+ else
63
+ redis.client.id
64
+ end
65
+ end
66
+
67
+ # The `before_first_fork` hook will be run in the **parent** process
68
+ # only once, before forking to run the first job. Be careful- any
69
+ # changes you make will be permanent for the lifespan of the
70
+ # worker.
71
+ #
72
+ # Call with a block to set the hook.
73
+ # Call with no arguments to return the hook.
74
+ def before_first_fork(&block)
75
+ block ? (@before_first_fork = block) : @before_first_fork
76
+ end
77
+
78
+ # Set a proc that will be called in the parent process before the
79
+ # worker forks for the first time.
80
+ attr_writer :before_first_fork
81
+
82
+ # The `before_fork` hook will be run in the **parent** process
83
+ # before every job, so be careful- any changes you make will be
84
+ # permanent for the lifespan of the worker.
85
+ #
86
+ # Call with a block to set the hook.
87
+ # Call with no arguments to return the hook.
88
+ def before_fork(&block)
89
+ block ? (@before_fork = block) : @before_fork
90
+ end
91
+
92
+ # Set the before_fork proc.
93
+ attr_writer :before_fork
94
+
95
+ # The `after_fork` hook will be run in the child process and is passed
96
+ # the current job. Any changes you make, therefore, will only live as
97
+ # long as the job currently being processed.
98
+ #
99
+ # Call with a block to set the hook.
100
+ # Call with no arguments to return the hook.
101
+ def after_fork(&block)
102
+ block ? (@after_fork = block) : @after_fork
103
+ end
104
+
105
+ # Set the after_fork proc.
106
+ attr_writer :after_fork
107
+
108
+ def to_s
109
+ "Resque Client connected to #{redis_id}"
110
+ end
111
+
112
+ attr_accessor :inline
113
+
114
+ # If 'inline' is true Resque will call #perform method inline
115
+ # without queuing it into Redis and without any Resque callbacks.
116
+ # The 'inline' is false Resque jobs will be put in queue regularly.
117
+ alias :inline? :inline
118
+
119
+ #
120
+ # queue manipulation
121
+ #
122
+
123
+ # Pushes a job onto a queue. Queue name should be a string and the
124
+ # item should be any JSON-able Ruby object.
125
+ #
126
+ # Resque works generally expect the `item` to be a hash with the following
127
+ # keys:
128
+ #
129
+ # class - The String name of the job to run.
130
+ # args - An Array of arguments to pass the job. Usually passed
131
+ # via `class.to_class.perform(*args)`.
132
+ #
133
+ # Example
134
+ #
135
+ # Resque.push('archive', :class => 'Archive', :args => [ 35, 'tar' ])
136
+ #
137
+ # Returns nothing
138
+ def push(queue, item)
139
+ watch_queue(queue)
140
+ redis.rpush "queue:#{queue}", encode(item)
141
+ end
142
+
143
+ # Pops a job off a queue. Queue name should be a string.
144
+ #
145
+ # Returns a Ruby object.
146
+ def pop(queue)
147
+ decode redis.lpop("queue:#{queue}")
148
+ end
149
+
150
+ # Returns an integer representing the size of a queue.
151
+ # Queue name should be a string.
152
+ def size(queue)
153
+ redis.llen("queue:#{queue}").to_i
154
+ end
155
+
156
+ # Returns an array of items currently queued. Queue name should be
157
+ # a string.
158
+ #
159
+ # start and count should be integer and can be used for pagination.
160
+ # start is the item to begin, count is how many items to return.
161
+ #
162
+ # To get the 3rd page of a 30 item, paginatied list one would use:
163
+ # Resque.peek('my_list', 59, 30)
164
+ def peek(queue, start = 0, count = 1)
165
+ list_range("queue:#{queue}", start, count)
166
+ end
167
+
168
+ # Does the dirty work of fetching a range of items from a Redis list
169
+ # and converting them into Ruby objects.
170
+ def list_range(key, start = 0, count = 1)
171
+ if count == 1
172
+ decode redis.lindex(key, start)
173
+ else
174
+ Array(redis.lrange(key, start, start+count-1)).map do |item|
175
+ decode item
176
+ end
177
+ end
178
+ end
179
+
180
+ # Returns an array of all known Resque queues as strings.
181
+ def queues
182
+ Array(redis.smembers(:queues))
183
+ end
184
+
185
+ # Given a queue name, completely deletes the queue.
186
+ def remove_queue(queue)
187
+ redis.srem(:queues, queue.to_s)
188
+ redis.del("queue:#{queue}")
189
+ end
190
+
191
+ # Used internally to keep track of which queues we've created.
192
+ # Don't call this directly.
193
+ def watch_queue(queue)
194
+ redis.sadd(:queues, queue.to_s)
195
+ end
196
+
197
+
198
+ #
199
+ # job shortcuts
200
+ #
201
+
202
+ # This method can be used to conveniently add a job to a queue.
203
+ # It assumes the class you're passing it is a real Ruby class (not
204
+ # a string or reference) which either:
205
+ #
206
+ # a) has a @queue ivar set
207
+ # b) responds to `queue`
208
+ #
209
+ # If either of those conditions are met, it will use the value obtained
210
+ # from performing one of the above operations to determine the queue.
211
+ #
212
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
213
+ #
214
+ # Returns true if the job was queued, nil if the job was rejected by a
215
+ # before_enqueue hook.
216
+ #
217
+ # This method is considered part of the `stable` API.
218
+ def enqueue(klass, *args)
219
+ enqueue_to(queue_from_class(klass), klass, *args)
220
+ end
221
+
222
+ # Just like `enqueue` but allows you to specify the queue you want to
223
+ # use. Runs hooks.
224
+ #
225
+ # `queue` should be the String name of the queue you're targeting.
226
+ #
227
+ # Returns true if the job was queued, nil if the job was rejected by a
228
+ # before_enqueue hook.
229
+ #
230
+ # This method is considered part of the `stable` API.
231
+ def enqueue_to(queue, klass, *args)
232
+ # Perform before_enqueue hooks. Don't perform enqueue if any hook returns false
233
+ before_hooks = Plugin.before_enqueue_hooks(klass).collect do |hook|
234
+ klass.send(hook, *args)
235
+ end
236
+ return nil if before_hooks.any? { |result| result == false }
237
+
238
+ Job.create(queue, klass, *args)
239
+
240
+ Plugin.after_enqueue_hooks(klass).each do |hook|
241
+ klass.send(hook, *args)
242
+ end
243
+
244
+ return true
245
+ end
246
+
247
+ # This method can be used to conveniently remove a job from a queue.
248
+ # It assumes the class you're passing it is a real Ruby class (not
249
+ # a string or reference) which either:
250
+ #
251
+ # a) has a @queue ivar set
252
+ # b) responds to `queue`
253
+ #
254
+ # If either of those conditions are met, it will use the value obtained
255
+ # from performing one of the above operations to determine the queue.
256
+ #
257
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
258
+ #
259
+ # If no args are given, this method will dequeue *all* jobs matching
260
+ # the provided class. See `Resque::Job.destroy` for more
261
+ # information.
262
+ #
263
+ # Returns the number of jobs destroyed.
264
+ #
265
+ # Example:
266
+ #
267
+ # # Removes all jobs of class `UpdateNetworkGraph`
268
+ # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph)
269
+ #
270
+ # # Removes all jobs of class `UpdateNetworkGraph` with matching args.
271
+ # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph, 'repo:135325')
272
+ #
273
+ # This method is considered part of the `stable` API.
274
+ def dequeue(klass, *args)
275
+ # Perform before_dequeue hooks. Don't perform dequeue if any hook returns false
276
+ before_hooks = Plugin.before_dequeue_hooks(klass).collect do |hook|
277
+ klass.send(hook, *args)
278
+ end
279
+ return if before_hooks.any? { |result| result == false }
280
+
281
+ Job.destroy(queue_from_class(klass), klass, *args)
282
+
283
+ Plugin.after_dequeue_hooks(klass).each do |hook|
284
+ klass.send(hook, *args)
285
+ end
286
+ end
287
+
288
+ # Given a class, try to extrapolate an appropriate queue based on a
289
+ # class instance variable or `queue` method.
290
+ def queue_from_class(klass)
291
+ klass.instance_variable_get(:@queue) ||
292
+ (klass.respond_to?(:queue) and klass.queue)
293
+ end
294
+
295
+ # This method will return a `Resque::Job` object or a non-true value
296
+ # depending on whether a job can be obtained. You should pass it the
297
+ # precise name of a queue: case matters.
298
+ #
299
+ # This method is considered part of the `stable` API.
300
+ def reserve(queue)
301
+ Job.reserve(queue)
302
+ end
303
+
304
+ # Validates if the given klass could be a valid Resque job
305
+ #
306
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
307
+ #
308
+ # If given klass is nil this method will raise a `Resque::NoClassError`
309
+ def validate(klass, queue = nil)
310
+ queue ||= queue_from_class(klass)
311
+
312
+ if !queue
313
+ raise NoQueueError.new("Jobs must be placed onto a queue.")
314
+ end
315
+
316
+ if klass.to_s.empty?
317
+ raise NoClassError.new("Jobs must be given a class.")
318
+ end
319
+ end
320
+
321
+
322
+ #
323
+ # worker shortcuts
324
+ #
325
+
326
+ # A shortcut to Worker.all
327
+ def workers
328
+ Worker.all
329
+ end
330
+
331
+ # A shortcut to Worker.working
332
+ def working
333
+ Worker.working
334
+ end
335
+
336
+ # A shortcut to unregister_worker
337
+ # useful for command line tool
338
+ def remove_worker(worker_id)
339
+ worker = Resque::Worker.find(worker_id)
340
+ worker.unregister_worker
341
+ end
342
+
343
+ #
344
+ # stats
345
+ #
346
+
347
+ # Returns a hash, similar to redis-rb's #info, of interesting stats.
348
+ def info
349
+ return {
350
+ :pending => queues.inject(0) { |m,k| m + size(k) },
351
+ :processed => Stat[:processed],
352
+ :queues => queues.size,
353
+ :workers => workers.size.to_i,
354
+ :working => working.size,
355
+ :failed => Stat[:failed],
356
+ :servers => [redis_id],
357
+ :environment => ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
358
+ }
359
+ end
360
+
361
+ # Returns an array of all known Resque keys in Redis. Redis' KEYS operation
362
+ # is O(N) for the keyspace, so be careful - this can be slow for big databases.
363
+ def keys
364
+ redis.keys("*").map do |key|
365
+ key.sub("#{redis.namespace}:", '')
366
+ end
367
+ end
368
+ end
369
+