resque-cedar 1.20.0

Sign up to get free protection for your applications and to get access to all the features.
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
+