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