scotttam-resque 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.gitignore +3 -0
  2. data/.kick +26 -0
  3. data/CONTRIBUTORS +23 -0
  4. data/HISTORY.md +80 -0
  5. data/LICENSE +20 -0
  6. data/README.markdown +767 -0
  7. data/Rakefile +66 -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/errors.rb +7 -0
  24. data/lib/resque/failure/base.rb +58 -0
  25. data/lib/resque/failure/hoptoad.rb +121 -0
  26. data/lib/resque/failure/multiple.rb +44 -0
  27. data/lib/resque/failure/redis.rb +33 -0
  28. data/lib/resque/failure.rb +63 -0
  29. data/lib/resque/helpers.rb +57 -0
  30. data/lib/resque/job.rb +146 -0
  31. data/lib/resque/server/public/idle.png +0 -0
  32. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  33. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  34. data/lib/resque/server/public/poll.png +0 -0
  35. data/lib/resque/server/public/ranger.js +24 -0
  36. data/lib/resque/server/public/reset.css +48 -0
  37. data/lib/resque/server/public/style.css +76 -0
  38. data/lib/resque/server/public/working.png +0 -0
  39. data/lib/resque/server/views/error.erb +1 -0
  40. data/lib/resque/server/views/failed.erb +35 -0
  41. data/lib/resque/server/views/key.erb +17 -0
  42. data/lib/resque/server/views/layout.erb +38 -0
  43. data/lib/resque/server/views/next_more.erb +10 -0
  44. data/lib/resque/server/views/overview.erb +4 -0
  45. data/lib/resque/server/views/queues.erb +46 -0
  46. data/lib/resque/server/views/stats.erb +62 -0
  47. data/lib/resque/server/views/workers.erb +78 -0
  48. data/lib/resque/server/views/working.erb +69 -0
  49. data/lib/resque/server.rb +187 -0
  50. data/lib/resque/stat.rb +53 -0
  51. data/lib/resque/tasks.rb +39 -0
  52. data/lib/resque/version.rb +3 -0
  53. data/lib/resque/worker.rb +453 -0
  54. data/lib/resque.rb +246 -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 +220 -0
  59. data/test/test_helper.rb +96 -0
  60. data/test/worker_test.rb +260 -0
  61. metadata +172 -0
@@ -0,0 +1,453 @@
1
+ module Resque
2
+ # A Resque Worker processes jobs. On platforms that support fork(2),
3
+ # the worker will fork off a child to process each job. This ensures
4
+ # a clean slate when beginning the next job and cuts down on gradual
5
+ # memory growth as well as low level failures.
6
+ #
7
+ # It also ensures workers are always listening to signals from you,
8
+ # their master, and can react accordingly.
9
+ class Worker
10
+ include Resque::Helpers
11
+ extend Resque::Helpers
12
+
13
+ # Whether the worker should log basic info to STDOUT
14
+ attr_accessor :verbose
15
+
16
+ # Whether the worker should log lots of info to STDOUT
17
+ attr_accessor :very_verbose
18
+
19
+ # Boolean indicating whether this worker can or can not fork.
20
+ # Automatically set if a fork(2) fails.
21
+ attr_accessor :cant_fork
22
+
23
+ attr_writer :to_s
24
+
25
+ # Returns an array of all worker objects.
26
+ def self.all
27
+ redis.smembers(:workers).map { |id| find(id) }
28
+ end
29
+
30
+ # Returns an array of all worker objects currently processing
31
+ # jobs.
32
+ def self.working
33
+ names = all
34
+ return [] unless names.any?
35
+ names.map! { |name| "worker:#{name}" }
36
+ redis.mapped_mget(*names).keys.map do |key|
37
+ find key.sub("worker:", '')
38
+ end
39
+ end
40
+
41
+ # Returns a single worker object. Accepts a string id.
42
+ def self.find(worker_id)
43
+ if exists? worker_id
44
+ queues = worker_id.split(':')[-1].split(',')
45
+ worker = new(*queues)
46
+ worker.to_s = worker_id
47
+ worker
48
+ else
49
+ nil
50
+ end
51
+ end
52
+
53
+ # Alias of `find`
54
+ def self.attach(worker_id)
55
+ find(worker_id)
56
+ end
57
+
58
+ # Given a string worker id, return a boolean indicating whether the
59
+ # worker exists
60
+ def self.exists?(worker_id)
61
+ redis.sismember(:workers, worker_id)
62
+ end
63
+
64
+ #Sets the before_hook proc
65
+ def self.before_fork=(before_fork)
66
+ @@before_fork = before_fork
67
+ end
68
+
69
+ # Workers should be initialized with an array of string queue
70
+ # names. The order is important: a Worker will check the first
71
+ # queue given for a job. If none is found, it will check the
72
+ # second queue name given. If a job is found, it will be
73
+ # processed. Upon completion, the Worker will again check the
74
+ # first queue given, and so forth. In this way the queue list
75
+ # passed to a Worker on startup defines the priorities of queues.
76
+ #
77
+ # If passed a single "*", this Worker will operate on all queues
78
+ # in alphabetical order. Queues can be dynamically added or
79
+ # removed without needing to restart workers using this method.
80
+ def initialize(*queues)
81
+ @queues = queues
82
+ validate_queues
83
+ end
84
+
85
+ # A worker must be given a queue, otherwise it won't know what to
86
+ # do with itself.
87
+ #
88
+ # You probably never need to call this.
89
+ def validate_queues
90
+ if @queues.nil? || @queues.empty?
91
+ raise NoQueueError.new("Please give each worker at least one queue.")
92
+ end
93
+ end
94
+
95
+ # This is the main workhorse method. Called on a Worker instance,
96
+ # it begins the worker life cycle.
97
+ #
98
+ # The following events occur during a worker's life cycle:
99
+ #
100
+ # 1. Startup: Signals are registered, dead workers are pruned,
101
+ # and this worker is registered.
102
+ # 2. Work loop: Jobs are pulled from a queue and processed.
103
+ # 3. Teardown: This worker is unregistered.
104
+ #
105
+ # Can be passed an integer representing the polling frequency.
106
+ # The default is 5 seconds, but for a semi-active site you may
107
+ # want to use a smaller value.
108
+ #
109
+ # Also accepts a block which will be passed the job as soon as it
110
+ # has completed processing. Useful for testing.
111
+ def work(interval = 5, &block)
112
+ $0 = "resque: Starting"
113
+ startup
114
+
115
+ loop do
116
+ break if @shutdown
117
+
118
+ if not @paused and job = reserve
119
+ log "got: #{job.inspect}"
120
+
121
+ if @child = fork
122
+ rand # Reseeding
123
+ procline "Forked #{@child} at #{Time.now.to_i}"
124
+ Process.wait
125
+ else
126
+ procline "Processing #{job.queue} since #{Time.now.to_i}"
127
+ process(job, &block)
128
+ exit! unless @cant_fork
129
+ end
130
+
131
+ @child = nil
132
+ else
133
+ break if interval.to_i == 0
134
+ log! "Sleeping for #{interval.to_i}"
135
+ procline @paused ? "Paused" : "Waiting for #{@queues.join(',')}"
136
+ sleep interval.to_i
137
+ end
138
+ end
139
+
140
+ ensure
141
+ unregister_worker
142
+ end
143
+
144
+ # Processes a single job. If none is given, it will try to produce
145
+ # one.
146
+ def process(job = nil)
147
+ return unless job ||= reserve
148
+
149
+ begin
150
+ working_on job
151
+ job.perform
152
+ rescue Object => e
153
+ log "#{job.inspect} failed: #{e.inspect}"
154
+ job.fail(e)
155
+ failed!
156
+ else
157
+ log "done: #{job.inspect}"
158
+ ensure
159
+ yield job if block_given?
160
+ done_working
161
+ end
162
+ end
163
+
164
+ # Attempts to grab a job off one of the provided queues. Returns
165
+ # nil if no job can be found.
166
+ def reserve
167
+ queues.each do |queue|
168
+ log! "Checking #{queue}"
169
+ if job = Resque::Job.reserve(queue)
170
+ log! "Found job on #{queue}"
171
+ return job
172
+ end
173
+ end
174
+
175
+ nil
176
+ end
177
+
178
+ # Returns a list of queues to use when searching for a job.
179
+ # A splat ("*") means you want every queue (in alpha order) - this
180
+ # can be useful for dynamically adding new queues.
181
+ def queues
182
+ @queues[0] == "*" ? Resque.queues.sort : @queues
183
+ end
184
+
185
+ # Not every platform supports fork. Here we do our magic to
186
+ # determine if yours does.
187
+ def fork
188
+ @cant_fork = true if $TESTING
189
+
190
+ return if @cant_fork
191
+
192
+ begin
193
+ # IronRuby doesn't support `Kernel.fork` yet
194
+ if Kernel.respond_to?(:fork)
195
+ Kernel.fork
196
+ else
197
+ raise NotImplementedError
198
+ end
199
+ rescue NotImplementedError
200
+ @cant_fork = true
201
+ nil
202
+ end
203
+ end
204
+
205
+ # Runs all the methods needed when a worker begins its lifecycle.
206
+ def startup
207
+ enable_gc_optimizations
208
+ register_signal_handlers
209
+ prune_dead_workers
210
+ before_fork
211
+ register_worker
212
+ end
213
+
214
+ # Enables GC Optimizations if you're running REE.
215
+ # http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
216
+ def enable_gc_optimizations
217
+ if GC.respond_to?(:copy_on_write_friendly=)
218
+ GC.copy_on_write_friendly = true
219
+ end
220
+ end
221
+
222
+ # Registers the various signal handlers a worker responds to.
223
+ #
224
+ # TERM: Shutdown immediately, stop processing jobs.
225
+ # INT: Shutdown immediately, stop processing jobs.
226
+ # QUIT: Shutdown after the current job has finished processing.
227
+ # USR1: Kill the forked child immediately, continue processing jobs.
228
+ # USR2: Don't process any new jobs
229
+ # CONT: Start processing jobs again after a USR2
230
+ def register_signal_handlers
231
+ trap('TERM') { shutdown! }
232
+ trap('INT') { shutdown! }
233
+
234
+ begin
235
+ trap('QUIT') { shutdown }
236
+ trap('USR1') { kill_child }
237
+ trap('USR2') { pause_processing }
238
+ trap('CONT') { unpause_processing }
239
+ rescue ArgumentError
240
+ warn "Signals QUIT, USR1, USR2, and/or CONT not supported."
241
+ end
242
+
243
+ log! "Registered signals"
244
+ end
245
+
246
+ # Schedule this worker for shutdown. Will finish processing the
247
+ # current job.
248
+ def shutdown
249
+ log 'Exiting...'
250
+ @shutdown = true
251
+ end
252
+
253
+ # Kill the child and shutdown immediately.
254
+ def shutdown!
255
+ shutdown
256
+ kill_child
257
+ end
258
+
259
+ # Kills the forked child immediately, without remorse. The job it
260
+ # is processing will not be completed.
261
+ def kill_child
262
+ if @child
263
+ log! "Killing child at #{@child}"
264
+ if system("ps -o pid,state -p #{@child}")
265
+ Process.kill("KILL", @child) rescue nil
266
+ else
267
+ log! "Child #{@child} not found, restarting."
268
+ shutdown
269
+ end
270
+ end
271
+ end
272
+
273
+ # Stop processing jobs after the current one has completed (if we're
274
+ # currently running one).
275
+ def pause_processing
276
+ log "USR2 received; pausing job processing"
277
+ @paused = true
278
+ end
279
+
280
+ # Start processing jobs again after a pause
281
+ def unpause_processing
282
+ log "CONT received; resuming job processing"
283
+ @paused = false
284
+ end
285
+
286
+ # Looks for any workers which should be running on this server
287
+ # and, if they're not, removes them from Redis.
288
+ #
289
+ # This is a form of garbage collection. If a server is killed by a
290
+ # hard shutdown, power failure, or something else beyond our
291
+ # control, the Resque workers will not die gracefully and therefore
292
+ # will leave stale state information in Redis.
293
+ #
294
+ # By checking the current Redis state against the actual
295
+ # environment, we can determine if Redis is old and clean it up a bit.
296
+ def prune_dead_workers
297
+ all_workers = Worker.all
298
+ known_workers = worker_pids unless all_workers.empty?
299
+ all_workers.each do |worker|
300
+ host, pid, queues = worker.id.split(':')
301
+ next unless host == hostname
302
+ next if known_workers.include?(pid)
303
+ log! "Pruning dead worker: #{worker}"
304
+ worker.unregister_worker
305
+ end
306
+ end
307
+
308
+ # Registers ourself as a worker. Useful when entering the worker
309
+ # lifecycle on startup.
310
+ def register_worker
311
+ redis.sadd(:workers, self)
312
+ started!
313
+ end
314
+
315
+ #Call any before_fork procs, if any
316
+ def before_fork
317
+ @@before_fork.call if Worker.class_variable_defined?(:@@before_fork)
318
+ end
319
+
320
+ # Unregisters ourself as a worker. Useful when shutting down.
321
+ def unregister_worker
322
+ redis.srem(:workers, self)
323
+ redis.del("worker:#{self}:started")
324
+
325
+ Stat.clear("processed:#{self}")
326
+ Stat.clear("failed:#{self}")
327
+ end
328
+
329
+ # Given a job, tells Redis we're working on it. Useful for seeing
330
+ # what workers are doing and when.
331
+ def working_on(job)
332
+ job.worker = self
333
+ data = encode \
334
+ :queue => job.queue,
335
+ :run_at => Time.now.to_s,
336
+ :payload => job.payload
337
+ redis.set("worker:#{self}", data)
338
+ end
339
+
340
+ # Called when we are done working - clears our `working_on` state
341
+ # and tells Redis we processed a job.
342
+ def done_working
343
+ processed!
344
+ redis.del("worker:#{self}")
345
+ end
346
+
347
+ # How many jobs has this worker processed? Returns an int.
348
+ def processed
349
+ Stat["processed:#{self}"]
350
+ end
351
+
352
+ # Tell Redis we've processed a job.
353
+ def processed!
354
+ Stat << "processed"
355
+ Stat << "processed:#{self}"
356
+ end
357
+
358
+ # How many failed jobs has this worker seen? Returns an int.
359
+ def failed
360
+ Stat["failed:#{self}"]
361
+ end
362
+
363
+ # Tells Redis we've failed a job.
364
+ def failed!
365
+ Stat << "failed"
366
+ Stat << "failed:#{self}"
367
+ end
368
+
369
+ # What time did this worker start? Returns an instance of `Time`
370
+ def started
371
+ redis.get "worker:#{self}:started"
372
+ end
373
+
374
+ # Tell Redis we've started
375
+ def started!
376
+ redis.set("worker:#{self}:started", Time.now.to_s)
377
+ end
378
+
379
+ # Returns a hash explaining the Job we're currently processing, if any.
380
+ def job
381
+ decode(redis.get("worker:#{self}")) || {}
382
+ end
383
+ alias_method :processing, :job
384
+
385
+ # Boolean - true if working, false if not
386
+ def working?
387
+ state == :working
388
+ end
389
+
390
+ # Boolean - true if idle, false if not
391
+ def idle?
392
+ state == :idle
393
+ end
394
+
395
+ # Returns a symbol representing the current worker state,
396
+ # which can be either :working or :idle
397
+ def state
398
+ redis.exists("worker:#{self}") ? :working : :idle
399
+ end
400
+
401
+ # Is this worker the same as another worker?
402
+ def ==(other)
403
+ to_s == other.to_s
404
+ end
405
+
406
+ def inspect
407
+ "#<Worker #{to_s}>"
408
+ end
409
+
410
+ # The string representation is the same as the id for this worker
411
+ # instance. Can be used with `Worker.find`.
412
+ def to_s
413
+ @to_s ||= "#{hostname}:#{Process.pid}:#{@queues.join(',')}"
414
+ end
415
+ alias_method :id, :to_s
416
+
417
+ # chomp'd hostname of this machine
418
+ def hostname
419
+ @hostname ||= `hostname`.chomp
420
+ end
421
+
422
+ # Returns an array of string pids of all the other workers on this
423
+ # machine. Useful when pruning dead workers on startup.
424
+ def worker_pids
425
+ `ps -A -o pid,command | grep [r]esque`.split("\n").map do |line|
426
+ line.split(' ')[0]
427
+ end
428
+ end
429
+
430
+ # Given a string, sets the procline ($0) and logs.
431
+ # Procline is always in the format of:
432
+ # resque-VERSION: STRING
433
+ def procline(string)
434
+ $0 = "resque-#{Resque::Version}: #{string}"
435
+ log! $0
436
+ end
437
+
438
+ # Log a message to STDOUT if we are verbose or very_verbose.
439
+ def log(message)
440
+ if verbose
441
+ puts "*** #{message}"
442
+ elsif very_verbose
443
+ time = Time.now.strftime('%I:%M:%S %Y-%m-%d')
444
+ puts "** [#{time}] #$$: #{message}"
445
+ end
446
+ end
447
+
448
+ # Logs a very verbose message to STDOUT.
449
+ def log!(message)
450
+ log message if very_verbose
451
+ end
452
+ end
453
+ end
data/lib/resque.rb ADDED
@@ -0,0 +1,246 @@
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
+ #Set a proc that will be called once before the worker forks
52
+ def before_fork=(before_fork)
53
+ @before_fork = before_fork
54
+ end
55
+
56
+ #Returns the before_fork proc
57
+ def before_fork
58
+ @before_fork
59
+ end
60
+
61
+ def to_s
62
+ "Resque Client connected to #{redis.server}"
63
+ end
64
+
65
+
66
+ #
67
+ # queue manipulation
68
+ #
69
+
70
+ # Pushes a job onto a queue. Queue name should be a string and the
71
+ # item should be any JSON-able Ruby object.
72
+ def push(queue, item)
73
+ watch_queue(queue)
74
+ redis.rpush "queue:#{queue}", encode(item)
75
+ end
76
+
77
+ # Pops a job off a queue. Queue name should be a string.
78
+ #
79
+ # Returns a Ruby object.
80
+ def pop(queue)
81
+ decode redis.lpop("queue:#{queue}")
82
+ end
83
+
84
+ # Returns an int representing the size of a queue.
85
+ # Queue name should be a string.
86
+ def size(queue)
87
+ redis.llen("queue:#{queue}").to_i
88
+ end
89
+
90
+ # Returns an array of items currently queued. Queue name should be
91
+ # a string.
92
+ #
93
+ # start and count should be integer and can be used for pagination.
94
+ # start is the item to begin, count is how many items to return.
95
+ #
96
+ # To get the 3rd page of a 30 item, paginatied list one would use:
97
+ # Resque.peek('my_list', 59, 30)
98
+ def peek(queue, start = 0, count = 1)
99
+ list_range("queue:#{queue}", start, count)
100
+ end
101
+
102
+ # Does the dirty work of fetching a range of items from a Redis list
103
+ # and converting them into Ruby objects.
104
+ def list_range(key, start = 0, count = 1)
105
+ if count == 1
106
+ decode redis.lindex(key, start)
107
+ else
108
+ Array(redis.lrange(key, start, start+count-1)).map do |item|
109
+ decode item
110
+ end
111
+ end
112
+ end
113
+
114
+ # Returns an array of all known Resque queues as strings.
115
+ def queues
116
+ redis.smembers(:queues)
117
+ end
118
+
119
+ # Given a queue name, completely deletes the queue.
120
+ def remove_queue(queue)
121
+ redis.srem(:queues, queue.to_s)
122
+ redis.del("queue:#{queue}")
123
+ end
124
+
125
+ # Used internally to keep track of which queues we've created.
126
+ # Don't call this directly.
127
+ def watch_queue(queue)
128
+ redis.sadd(:queues, queue.to_s)
129
+ end
130
+
131
+
132
+ #
133
+ # job shortcuts
134
+ #
135
+
136
+ # This method can be used to conveniently add a job to a queue.
137
+ # It assumes the class you're passing it is a real Ruby class (not
138
+ # a string or reference) which either:
139
+ #
140
+ # a) has a @queue ivar set
141
+ # b) responds to `queue`
142
+ #
143
+ # If either of those conditions are met, it will use the value obtained
144
+ # from performing one of the above operations to determine the queue.
145
+ #
146
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
147
+ #
148
+ # This method is considered part of the `stable` API.
149
+ def enqueue(klass, *args)
150
+ Job.create(queue_from_class(klass), klass, *args)
151
+ end
152
+
153
+ # This method can be used to conveniently remove a job from a queue.
154
+ # It assumes the class you're passing it is a real Ruby class (not
155
+ # a string or reference) which either:
156
+ #
157
+ # a) has a @queue ivar set
158
+ # b) responds to `queue`
159
+ #
160
+ # If either of those conditions are met, it will use the value obtained
161
+ # from performing one of the above operations to determine the queue.
162
+ #
163
+ # If no queue can be inferred this method will raise a `Resque::NoQueueError`
164
+ #
165
+ # If no args are given, this method will dequeue *all* jobs matching
166
+ # the provided class. See `Resque::Job.destroy` for more
167
+ # information.
168
+ #
169
+ # Returns the number of jobs destroyed.
170
+ #
171
+ # Example:
172
+ #
173
+ # # Removes all jobs of class `UpdateNetworkGraph`
174
+ # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph)
175
+ #
176
+ # # Removes all jobs of class `UpdateNetworkGraph` with matching args.
177
+ # Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph, 'repo:135325')
178
+ #
179
+ # This method is considered part of the `stable` API.
180
+ def dequeue(klass, *args)
181
+ Job.destroy(queue_from_class(klass), klass, *args)
182
+ end
183
+
184
+ # Given a class, try to extrapolate an appropriate queue based on a
185
+ # class instance variable or `queue` method.
186
+ def queue_from_class(klass)
187
+ klass.instance_variable_get(:@queue) ||
188
+ (klass.respond_to?(:queue) and klass.queue)
189
+ end
190
+
191
+ # This method will return a `Resque::Job` object or a non-true value
192
+ # depending on whether a job can be obtained. You should pass it the
193
+ # precise name of a queue: case matters.
194
+ #
195
+ # This method is considered part of the `stable` API.
196
+ def reserve(queue)
197
+ Job.reserve(queue)
198
+ end
199
+
200
+
201
+ #
202
+ # worker shortcuts
203
+ #
204
+
205
+ # A shortcut to Worker.all
206
+ def workers
207
+ Worker.all
208
+ end
209
+
210
+ # A shortcut to Worker.working
211
+ def working
212
+ Worker.working
213
+ end
214
+
215
+ # A shortcut to unregister_worker
216
+ # useful for command line tool
217
+ def remove_worker(worker_id)
218
+ worker = Resque::Worker.find(worker_id)
219
+ worker.unregister_worker
220
+ end
221
+
222
+ #
223
+ # stats
224
+ #
225
+
226
+ # Returns a hash, similar to redis-rb's #info, of interesting stats.
227
+ def info
228
+ return {
229
+ :pending => queues.inject(0) { |m,k| m + size(k) },
230
+ :processed => Stat[:processed],
231
+ :queues => queues.size,
232
+ :workers => workers.size.to_i,
233
+ :working => working.size,
234
+ :failed => Stat[:failed],
235
+ :servers => [redis.server]
236
+ }
237
+ end
238
+
239
+ # Returns an array of all known Resque keys in Redis. Redis' KEYS operation
240
+ # is O(N) for the keyspace, so be careful - this can be slow for big databases.
241
+ def keys
242
+ redis.keys("*").map do |key|
243
+ key.sub('resque:', '')
244
+ end
245
+ end
246
+ end