resqueue 1.0.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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY.md +488 -0
  3. data/LICENSE +20 -0
  4. data/README.markdown +920 -0
  5. data/Rakefile +57 -0
  6. data/bin/resque +81 -0
  7. data/bin/resque-web +31 -0
  8. data/lib/resque.rb +578 -0
  9. data/lib/resque/data_store.rb +326 -0
  10. data/lib/resque/errors.rb +21 -0
  11. data/lib/resque/failure.rb +119 -0
  12. data/lib/resque/failure/airbrake.rb +33 -0
  13. data/lib/resque/failure/base.rb +73 -0
  14. data/lib/resque/failure/multiple.rb +68 -0
  15. data/lib/resque/failure/redis.rb +128 -0
  16. data/lib/resque/failure/redis_multi_queue.rb +104 -0
  17. data/lib/resque/helpers.rb +48 -0
  18. data/lib/resque/job.rb +296 -0
  19. data/lib/resque/log_formatters/quiet_formatter.rb +7 -0
  20. data/lib/resque/log_formatters/verbose_formatter.rb +7 -0
  21. data/lib/resque/log_formatters/very_verbose_formatter.rb +8 -0
  22. data/lib/resque/logging.rb +18 -0
  23. data/lib/resque/plugin.rb +78 -0
  24. data/lib/resque/server.rb +299 -0
  25. data/lib/resque/server/helpers.rb +64 -0
  26. data/lib/resque/server/public/favicon.ico +0 -0
  27. data/lib/resque/server/public/idle.png +0 -0
  28. data/lib/resque/server/public/jquery-1.12.4.min.js +5 -0
  29. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  30. data/lib/resque/server/public/poll.png +0 -0
  31. data/lib/resque/server/public/ranger.js +78 -0
  32. data/lib/resque/server/public/reset.css +44 -0
  33. data/lib/resque/server/public/style.css +91 -0
  34. data/lib/resque/server/public/working.png +0 -0
  35. data/lib/resque/server/test_helper.rb +19 -0
  36. data/lib/resque/server/views/error.erb +1 -0
  37. data/lib/resque/server/views/failed.erb +29 -0
  38. data/lib/resque/server/views/failed_job.erb +50 -0
  39. data/lib/resque/server/views/failed_queues_overview.erb +24 -0
  40. data/lib/resque/server/views/key_sets.erb +17 -0
  41. data/lib/resque/server/views/key_string.erb +11 -0
  42. data/lib/resque/server/views/layout.erb +44 -0
  43. data/lib/resque/server/views/next_more.erb +22 -0
  44. data/lib/resque/server/views/overview.erb +4 -0
  45. data/lib/resque/server/views/queues.erb +58 -0
  46. data/lib/resque/server/views/stats.erb +62 -0
  47. data/lib/resque/server/views/workers.erb +111 -0
  48. data/lib/resque/server/views/working.erb +72 -0
  49. data/lib/resque/stat.rb +58 -0
  50. data/lib/resque/tasks.rb +72 -0
  51. data/lib/resque/thread_signal.rb +45 -0
  52. data/lib/resque/vendor/utf8_util.rb +26 -0
  53. data/lib/resque/vendor/utf8_util/utf8_util_18.rb +91 -0
  54. data/lib/resque/vendor/utf8_util/utf8_util_19.rb +6 -0
  55. data/lib/resque/version.rb +3 -0
  56. data/lib/resque/worker.rb +892 -0
  57. data/lib/resqueue.rb +4 -0
  58. data/lib/tasks/redis.rake +161 -0
  59. data/lib/tasks/resque.rake +2 -0
  60. metadata +197 -0
@@ -0,0 +1,57 @@
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
+ require 'rubygems'
15
+ require 'bundler/setup'
16
+ require 'bundler/gem_tasks'
17
+
18
+
19
+ #
20
+ # Tests
21
+ #
22
+
23
+ require 'rake/testtask'
24
+
25
+ task :default => :test
26
+
27
+ Rake::TestTask.new do |test|
28
+ test.verbose = true
29
+ test.libs << "test"
30
+ test.libs << "lib"
31
+ test.test_files = FileList['test/**/*_test.rb']
32
+ end
33
+
34
+ if command? :kicker
35
+ desc "Launch Kicker (like autotest)"
36
+ task :kicker do
37
+ puts "Kicking... (ctrl+c to cancel)"
38
+ exec "kicker -e rake test lib examples"
39
+ end
40
+ end
41
+
42
+
43
+ #
44
+ # Install
45
+ #
46
+
47
+ task :install => [ 'redis:install', 'dtach:install' ]
48
+
49
+
50
+ #
51
+ # Documentation
52
+ #
53
+
54
+ begin
55
+ require 'sdoc_helpers'
56
+ rescue LoadError
57
+ 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,31 @@
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
+ opts.on('-a url-prefix', "--append url-prefix", "set reverse_proxy friendly prefix to links") {|url_prefix|
28
+ runner.logger.info "Using URL Prefix '#{url_prefix}'"
29
+ Resque::Server.url_prefix = url_prefix
30
+ }
31
+ end
@@ -0,0 +1,578 @@
1
+ require 'mono_logger'
2
+ require 'redis/namespace'
3
+ require 'forwardable'
4
+
5
+ require 'resque/version'
6
+
7
+ require 'resque/errors'
8
+
9
+ require 'resque/failure'
10
+ require 'resque/failure/base'
11
+
12
+ require 'resque/helpers'
13
+ require 'resque/stat'
14
+ require 'resque/logging'
15
+ require 'resque/log_formatters/quiet_formatter'
16
+ require 'resque/log_formatters/verbose_formatter'
17
+ require 'resque/log_formatters/very_verbose_formatter'
18
+ require 'resque/job'
19
+ require 'resque/worker'
20
+ require 'resque/plugin'
21
+ require 'resque/data_store'
22
+ require 'resque/thread_signal'
23
+
24
+ require 'resque/vendor/utf8_util'
25
+
26
+ module Resque
27
+ include Helpers
28
+ extend self
29
+
30
+ # Given a Ruby object, returns a string suitable for storage in a
31
+ # queue.
32
+ def encode(object)
33
+ if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
34
+ MultiJson.dump object
35
+ else
36
+ MultiJson.encode object
37
+ end
38
+ end
39
+
40
+ # Given a string, returns a Ruby object.
41
+ def decode(object)
42
+ return unless object
43
+
44
+ begin
45
+ if MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
46
+ MultiJson.load object
47
+ else
48
+ MultiJson.decode object
49
+ end
50
+ rescue ::MultiJson::DecodeError => e
51
+ raise Helpers::DecodeException, e.message, e.backtrace
52
+ end
53
+ end
54
+
55
+ # Given a word with dashes, returns a camel cased version of it.
56
+ #
57
+ # classify('job-name') # => 'JobName'
58
+ def classify(dashed_word)
59
+ dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
60
+ end
61
+
62
+ # Tries to find a constant with the name specified in the argument string:
63
+ #
64
+ # constantize("Module") # => Module
65
+ # constantize("Test::Unit") # => Test::Unit
66
+ #
67
+ # The name is assumed to be the one of a top-level constant, no matter
68
+ # whether it starts with "::" or not. No lexical context is taken into
69
+ # account:
70
+ #
71
+ # C = 'outside'
72
+ # module M
73
+ # C = 'inside'
74
+ # C # => 'inside'
75
+ # constantize("C") # => 'outside', same as ::C
76
+ # end
77
+ #
78
+ # NameError is raised when the constant is unknown.
79
+ def constantize(camel_cased_word)
80
+ camel_cased_word = camel_cased_word.to_s
81
+
82
+ if camel_cased_word.include?('-')
83
+ camel_cased_word = classify(camel_cased_word)
84
+ end
85
+
86
+ names = camel_cased_word.split('::')
87
+ names.shift if names.empty? || names.first.empty?
88
+
89
+ constant = Object
90
+ names.each do |name|
91
+ args = Module.method(:const_get).arity != 1 ? [false] : []
92
+
93
+ if constant.const_defined?(name, *args)
94
+ constant = constant.const_get(name)
95
+ else
96
+ constant = constant.const_missing(name)
97
+ end
98
+ end
99
+ constant
100
+ end
101
+
102
+ extend ::Forwardable
103
+
104
+ # Accepts:
105
+ # 1. A 'hostname:port' String
106
+ # 2. A 'hostname:port:db' String (to select the Redis db)
107
+ # 3. A 'hostname:port/namespace' String (to set the Redis namespace)
108
+ # 4. A Redis URL String 'redis://host:port'
109
+ # 5. An instance of `Redis`, `Redis::Client`, `Redis::DistRedis`,
110
+ # or `Redis::Namespace`.
111
+ # 6. An Hash of a redis connection {:host => 'localhost', :port => 6379, :db => 0}
112
+ def redis=(server)
113
+ case server
114
+ when String
115
+ if server =~ /redis\:\/\//
116
+ redis = Redis.connect(:url => server, :thread_safe => true)
117
+ else
118
+ server, namespace = server.split('/', 2)
119
+ host, port, db = server.split(':')
120
+ redis = Redis.new(:host => host, :port => port,
121
+ :thread_safe => true, :db => db)
122
+ end
123
+ namespace ||= :resque
124
+
125
+ @data_store = Resque::DataStore.new(Redis::Namespace.new(namespace, :redis => redis))
126
+ when Redis::Namespace
127
+ @data_store = Resque::DataStore.new(server)
128
+ when Resque::DataStore
129
+ @data_store = server
130
+ when Hash
131
+ @data_store = Resque::DataStore.new(Redis::Namespace.new(:resque, :redis => Redis.new(server)))
132
+ else
133
+ @data_store = Resque::DataStore.new(Redis::Namespace.new(:resque, :redis => server))
134
+ end
135
+ end
136
+
137
+ # Returns the current Redis connection. If none has been created, will
138
+ # create a new one.
139
+ def redis
140
+ return @data_store if @data_store
141
+ self.redis = Redis.respond_to?(:connect) ? Redis.connect : "localhost:6379"
142
+ self.redis
143
+ end
144
+ alias :data_store :redis
145
+
146
+ def redis_id
147
+ data_store.identifier
148
+ end
149
+
150
+ # Set or retrieve the current logger object
151
+ attr_accessor :logger
152
+
153
+ DEFAULT_HEARTBEAT_INTERVAL = 60
154
+ DEFAULT_PRUNE_INTERVAL = DEFAULT_HEARTBEAT_INTERVAL * 5
155
+
156
+ attr_writer :heartbeat_interval
157
+ def heartbeat_interval
158
+ @heartbeat_interval || DEFAULT_HEARTBEAT_INTERVAL
159
+ end
160
+
161
+ attr_writer :prune_interval
162
+ def prune_interval
163
+ @prune_interval || DEFAULT_PRUNE_INTERVAL
164
+ end
165
+
166
+ attr_writer :enqueue_front
167
+ def enqueue_front
168
+ return @enqueue_front unless @enqueue_front.nil?
169
+ @enqueue_front = false
170
+ end
171
+
172
+ # The `before_first_fork` hook will be run in the **parent** process
173
+ # only once, before forking to run the first job. Be careful- any
174
+ # changes you make will be permanent for the lifespan of the
175
+ # worker.
176
+ #
177
+ # Call with a block to register a hook.
178
+ # Call with no arguments to return all registered hooks.
179
+ def before_first_fork(&block)
180
+ block ? register_hook(:before_first_fork, block) : hooks(:before_first_fork)
181
+ end
182
+
183
+ # Register a before_first_fork proc.
184
+ def before_first_fork=(block)
185
+ register_hook(:before_first_fork, block)
186
+ end
187
+
188
+ # The `before_fork` hook will be run in the **parent** process
189
+ # before every job, so be careful- any changes you make will be
190
+ # permanent for the lifespan of the worker.
191
+ #
192
+ # Call with a block to register a hook.
193
+ # Call with no arguments to return all registered hooks.
194
+ def before_fork(&block)
195
+ block ? register_hook(:before_fork, block) : hooks(:before_fork)
196
+ end
197
+
198
+ # Register a before_fork proc.
199
+ def before_fork=(block)
200
+ register_hook(:before_fork, block)
201
+ end
202
+
203
+ # The `after_fork` hook will be run in the child process and is passed
204
+ # the current job. Any changes you make, therefore, will only live as
205
+ # long as the job currently being processed.
206
+ #
207
+ # Call with a block to register a hook.
208
+ # Call with no arguments to return all registered hooks.
209
+ def after_fork(&block)
210
+ block ? register_hook(:after_fork, block) : hooks(:after_fork)
211
+ end
212
+
213
+ # Register an after_fork proc.
214
+ def after_fork=(block)
215
+ register_hook(:after_fork, block)
216
+ end
217
+
218
+ # The `before_pause` hook will be run in the parent process before the
219
+ # worker has paused processing (via #pause_processing or SIGUSR2).
220
+ def before_pause(&block)
221
+ block ? register_hook(:before_pause, block) : hooks(:before_pause)
222
+ end
223
+
224
+ # Register a before_pause proc.
225
+ def before_pause=(block)
226
+ register_hook(:before_pause, block)
227
+ end
228
+
229
+ # The `after_pause` hook will be run in the parent process after the
230
+ # worker has paused (via SIGCONT).
231
+ def after_pause(&block)
232
+ block ? register_hook(:after_pause, block) : hooks(:after_pause)
233
+ end
234
+
235
+ # Register an after_pause proc.
236
+ def after_pause=(block)
237
+ register_hook(:after_pause, block)
238
+ end
239
+
240
+ def to_s
241
+ "Resque Client connected to #{redis_id}"
242
+ end
243
+
244
+ attr_accessor :inline
245
+
246
+ # If 'inline' is true Resque will call #perform method inline
247
+ # without queuing it into Redis and without any Resque callbacks.
248
+ # The 'inline' is false Resque jobs will be put in queue regularly.
249
+ alias :inline? :inline
250
+
251
+ #
252
+ # queue manipulation
253
+ #
254
+
255
+ # Pushes a job onto a queue. Queue name should be a string and the
256
+ # item should be any JSON-able Ruby object.
257
+ #
258
+ # Resque works generally expect the `item` to be a hash with the following
259
+ # keys:
260
+ #
261
+ # class - The String name of the job to run.
262
+ # args - An Array of arguments to pass the job. Usually passed
263
+ # via `class.to_class.perform(*args)`.
264
+ #
265
+ # Example
266
+ #
267
+ # Resque.push('archive', :class => 'Archive', :args => [ 35, 'tar' ])
268
+ #
269
+ # Returns nothing
270
+ def push(queue, item)
271
+ data_store.push_to_queue(queue,encode(item))
272
+ end
273
+
274
+ # Pops a job off a queue. Queue name should be a string.
275
+ #
276
+ # Returns a Ruby object.
277
+ def pop(queue)
278
+ decode(data_store.pop_from_queue(queue))
279
+ end
280
+
281
+ # Returns an integer representing the size of a queue.
282
+ # Queue name should be a string.
283
+ def size(queue)
284
+ data_store.queue_size(queue)
285
+ end
286
+
287
+ # Returns an array of items currently queued. Queue name should be
288
+ # a string.
289
+ #
290
+ # start and count should be integer and can be used for pagination.
291
+ # start is the item to begin, count is how many items to return.
292
+ #
293
+ # To get the 3rd page of a 30 item, paginatied list one would use:
294
+ # Resque.peek('my_list', 59, 30)
295
+ def peek(queue, start = 0, count = 1)
296
+ results = data_store.peek_in_queue(queue,start,count)
297
+ if count == 1
298
+ decode(results)
299
+ else
300
+ results.map { |result| decode(result) }
301
+ end
302
+ end
303
+
304
+ # Does the dirty work of fetching a range of items from a Redis list
305
+ # and converting them into Ruby objects.
306
+ def list_range(key, start = 0, count = 1)
307
+ results = data_store.list_range(key, start, count)
308
+ if count == 1
309
+ decode(results)
310
+ else
311
+ results.map { |result| decode(result) }
312
+ end
313
+ end
314
+
315
+ # Returns an array of all known Resque queues as strings.
316
+ def queues
317
+ data_store.queue_names
318
+ end
319
+
320
+ # Given a queue name, completely deletes the queue.
321
+ def remove_queue(queue)
322
+ data_store.remove_queue(queue)
323
+ end
324
+
325
+ # Used internally to keep track of which queues we've created.
326
+ # Don't call this directly.
327
+ def watch_queue(queue)
328
+ data_store.watch_queue(queue)
329
+ end
330
+
331
+
332
+ #
333
+ # job shortcuts
334
+ #
335
+
336
+ # This method can be used to conveniently add a job to a queue.
337
+ # It assumes the class you're passing it is a real Ruby class (not
338
+ # a string or reference) which either:
339
+ #
340
+ # a) has a @queue ivar set
341
+ # b) responds to `queue`
342
+ #
343
+ # If either of those conditions are met, it will use the value obtained
344
+ # from performing one of the above operations to determine the queue.
345
+ #
346
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
347
+ #
348
+ # Returns true if the job was queued, nil if the job was rejected by a
349
+ # before_enqueue hook.
350
+ #
351
+ # This method is considered part of the `stable` API.
352
+ def enqueue(klass, *args)
353
+ enqueue_to(queue_from_class(klass), klass, *args)
354
+ end
355
+
356
+ # Just like `enqueue` but allows you to specify the queue you want to
357
+ # use. Runs hooks.
358
+ #
359
+ # `queue` should be the String name of the queue you're targeting.
360
+ #
361
+ # Returns true if the job was queued, nil if the job was rejected by a
362
+ # before_enqueue hook.
363
+ #
364
+ # This method is considered part of the `stable` API.
365
+ def enqueue_to(queue, klass, *args)
366
+ # Perform before_enqueue hooks. Don't perform enqueue if any hook returns false
367
+ before_hooks = Plugin.before_enqueue_hooks(klass).collect do |hook|
368
+ klass.send(hook, *args)
369
+ end
370
+ return nil if before_hooks.any? { |result| result == false }
371
+
372
+ Job.create(queue, klass, *args)
373
+
374
+ Plugin.after_enqueue_hooks(klass).each do |hook|
375
+ klass.send(hook, *args)
376
+ end
377
+
378
+ return true
379
+ end
380
+
381
+ # This method can be used to conveniently remove a job from a queue.
382
+ # It assumes the class you're passing it is a real Ruby class (not
383
+ # a string or reference) which either:
384
+ #
385
+ # a) has a @queue ivar set
386
+ # b) responds to `queue`
387
+ #
388
+ # If either of those conditions are met, it will use the value obtained
389
+ # from performing one of the above operations to determine the queue.
390
+ #
391
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
392
+ #
393
+ # If no args are given, this method will dequeue *all* jobs matching
394
+ # the provided class. See `Resque::Job.destroy` for more
395
+ # information.
396
+ #
397
+ # Returns the number of jobs destroyed.
398
+ #
399
+ # Example:
400
+ #
401
+ # # Removes all jobs of class `UpdateNetworkGraph`
402
+ # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph)
403
+ #
404
+ # # Removes all jobs of class `UpdateNetworkGraph` with matching args.
405
+ # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph, 'repo:135325')
406
+ #
407
+ # This method is considered part of the `stable` API.
408
+ def dequeue(klass, *args)
409
+ # Perform before_dequeue hooks. Don't perform dequeue if any hook returns false
410
+ before_hooks = Plugin.before_dequeue_hooks(klass).collect do |hook|
411
+ klass.send(hook, *args)
412
+ end
413
+ return if before_hooks.any? { |result| result == false }
414
+
415
+ destroyed = Job.destroy(queue_from_class(klass), klass, *args)
416
+
417
+ Plugin.after_dequeue_hooks(klass).each do |hook|
418
+ klass.send(hook, *args)
419
+ end
420
+
421
+ destroyed
422
+ end
423
+
424
+ # Given a class, try to extrapolate an appropriate queue based on a
425
+ # class instance variable or `queue` method.
426
+ def queue_from_class(klass)
427
+ klass.instance_variable_get(:@queue) ||
428
+ (klass.respond_to?(:queue) and klass.queue)
429
+ end
430
+
431
+ # This method will return a `Resque::Job` object or a non-true value
432
+ # depending on whether a job can be obtained. You should pass it the
433
+ # precise name of a queue: case matters.
434
+ #
435
+ # This method is considered part of the `stable` API.
436
+ def reserve(queue)
437
+ Job.reserve(queue)
438
+ end
439
+
440
+ # Validates if the given klass could be a valid Resque job
441
+ #
442
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
443
+ #
444
+ # If given klass is nil this method will raise a `Resque::NoClassError`
445
+ def validate(klass, queue = nil)
446
+ queue ||= queue_from_class(klass)
447
+
448
+ if !queue
449
+ raise NoQueueError.new("Jobs must be placed onto a queue. No queue could be inferred for class #{klass}")
450
+ end
451
+
452
+ if klass.to_s.empty?
453
+ raise NoClassError.new("Jobs must be given a class.")
454
+ end
455
+ end
456
+
457
+
458
+ #
459
+ # worker shortcuts
460
+ #
461
+
462
+ # A shortcut to Worker.all
463
+ def workers
464
+ Worker.all
465
+ end
466
+
467
+ # A shortcut to Worker.working
468
+ def working
469
+ Worker.working
470
+ end
471
+
472
+ # A shortcut to unregister_worker
473
+ # useful for command line tool
474
+ def remove_worker(worker_id)
475
+ worker = Resque::Worker.find(worker_id)
476
+ worker.unregister_worker
477
+ end
478
+
479
+ #
480
+ # stats
481
+ #
482
+
483
+ # Returns a hash, similar to redis-rb's #info, of interesting stats.
484
+ def info
485
+ return {
486
+ :pending => queue_sizes.inject(0) { |sum, (queue_name, queue_size)| sum + queue_size },
487
+ :processed => Stat[:processed],
488
+ :queues => queues.size,
489
+ :workers => workers.size.to_i,
490
+ :working => working.size,
491
+ :failed => data_store.num_failed,
492
+ :servers => [redis_id],
493
+ :environment => ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
494
+ }
495
+ end
496
+
497
+ # Returns an array of all known Resque keys in Redis. Redis' KEYS operation
498
+ # is O(N) for the keyspace, so be careful - this can be slow for big databases.
499
+ def keys
500
+ data_store.all_resque_keys
501
+ end
502
+
503
+ # Returns a hash, mapping queue names to queue sizes
504
+ def queue_sizes
505
+ queue_names = queues
506
+
507
+ sizes = redis.pipelined do
508
+ queue_names.each do |name|
509
+ redis.llen("queue:#{name}")
510
+ end
511
+ end
512
+
513
+ Hash[queue_names.zip(sizes)]
514
+ end
515
+
516
+ # Returns a hash, mapping queue names to (up to `sample_size`) samples of jobs in that queue
517
+ def sample_queues(sample_size = 1000)
518
+ queue_names = queues
519
+
520
+ samples = redis.pipelined do
521
+ queue_names.each do |name|
522
+ key = "queue:#{name}"
523
+ redis.llen(key)
524
+ redis.lrange(key, 0, sample_size - 1)
525
+ end
526
+ end
527
+
528
+ hash = {}
529
+
530
+ queue_names.zip(samples.each_slice(2).to_a) do |queue_name, (queue_size, serialized_samples)|
531
+ samples = serialized_samples.map do |serialized_sample|
532
+ Job.decode(serialized_sample)
533
+ end
534
+
535
+ hash[queue_name] = {
536
+ :size => queue_size,
537
+ :samples => samples
538
+ }
539
+ end
540
+
541
+ hash
542
+ end
543
+
544
+ private
545
+
546
+ # Register a new proc as a hook. If the block is nil this is the
547
+ # equivalent of removing all hooks of the given name.
548
+ #
549
+ # `name` is the hook that the block should be registered with.
550
+ def register_hook(name, block)
551
+ return clear_hooks(name) if block.nil?
552
+
553
+ @hooks ||= {}
554
+ @hooks[name] ||= []
555
+
556
+ block = Array(block)
557
+ @hooks[name].concat(block)
558
+ end
559
+
560
+ # Clear all hooks given a hook name.
561
+ def clear_hooks(name)
562
+ @hooks && @hooks[name] = []
563
+ end
564
+
565
+ # Retrieve all hooks
566
+ def hooks
567
+ @hooks || {}
568
+ end
569
+
570
+ # Retrieve all hooks of a given name.
571
+ def hooks(name)
572
+ (@hooks && @hooks[name]) || []
573
+ end
574
+ end
575
+
576
+ # Log to STDOUT by default
577
+ Resque.logger = MonoLogger.new(STDOUT)
578
+ Resque.logger.formatter = Resque::QuietFormatter.new