mongo-resque 1.17.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 (46) hide show
  1. data/HISTORY.md +278 -0
  2. data/LICENSE +20 -0
  3. data/README.markdown +76 -0
  4. data/bin/resque +67 -0
  5. data/bin/resque-web +23 -0
  6. data/docs/HOOKS.md +132 -0
  7. data/docs/PLUGINS.md +93 -0
  8. data/lib/resque/errors.rb +13 -0
  9. data/lib/resque/failure/base.rb +64 -0
  10. data/lib/resque/failure/hoptoad.rb +133 -0
  11. data/lib/resque/failure/mongo.rb +45 -0
  12. data/lib/resque/failure/multiple.rb +54 -0
  13. data/lib/resque/failure/redis.rb +46 -0
  14. data/lib/resque/failure.rb +70 -0
  15. data/lib/resque/helpers.rb +75 -0
  16. data/lib/resque/job.rb +215 -0
  17. data/lib/resque/plugin.rb +51 -0
  18. data/lib/resque/server/public/favicon.ico +0 -0
  19. data/lib/resque/server/public/idle.png +0 -0
  20. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  21. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  22. data/lib/resque/server/public/poll.png +0 -0
  23. data/lib/resque/server/public/ranger.js +73 -0
  24. data/lib/resque/server/public/reset.css +48 -0
  25. data/lib/resque/server/public/style.css +92 -0
  26. data/lib/resque/server/public/working.png +0 -0
  27. data/lib/resque/server/test_helper.rb +19 -0
  28. data/lib/resque/server/views/error.erb +1 -0
  29. data/lib/resque/server/views/failed.erb +65 -0
  30. data/lib/resque/server/views/key_sets.erb +19 -0
  31. data/lib/resque/server/views/key_string.erb +11 -0
  32. data/lib/resque/server/views/layout.erb +42 -0
  33. data/lib/resque/server/views/next_more.erb +10 -0
  34. data/lib/resque/server/views/overview.erb +4 -0
  35. data/lib/resque/server/views/queues.erb +69 -0
  36. data/lib/resque/server/views/stats.erb +73 -0
  37. data/lib/resque/server/views/workers.erb +109 -0
  38. data/lib/resque/server/views/working.erb +72 -0
  39. data/lib/resque/server.rb +268 -0
  40. data/lib/resque/stat.rb +54 -0
  41. data/lib/resque/tasks.rb +42 -0
  42. data/lib/resque/version.rb +3 -0
  43. data/lib/resque/worker.rb +497 -0
  44. data/lib/resque.rb +417 -0
  45. data/lib/tasks/resque.rake +2 -0
  46. metadata +148 -0
@@ -0,0 +1,54 @@
1
+ module Resque
2
+ # The stat subsystem. Used to keep track of integer counts.
3
+ #
4
+ # Get a stat: Stat[name]
5
+ # Incr a stat: Stat.incr(name)
6
+ # Decr a stat: Stat.decr(name)
7
+ # Kill a stat: Stat.clear(name)
8
+ module Stat
9
+ extend self
10
+ extend Helpers
11
+
12
+ # Returns the int value of a stat, given a string stat name.
13
+ def get(stat)
14
+ value = mongo_stats.find_one :stat => stat
15
+ value.nil? ? 0 : value['value']
16
+ end
17
+
18
+ # Alias of `get`
19
+ def [](stat)
20
+ get(stat)
21
+ end
22
+
23
+ # For a string stat name, increments the stat by one.
24
+ #
25
+ # Can optionally accept a second int parameter. The stat is then
26
+ # incremented by that amount.
27
+ def incr(stat, by = 1)
28
+ mongo_stats.update({:stat => stat}, {'$inc' => {:value => by}}, :upsert => true)
29
+ end
30
+
31
+ # Increments a stat by one.
32
+ def <<(stat)
33
+ incr stat
34
+ end
35
+
36
+ # For a string stat name, decrements the stat by one.
37
+ #
38
+ # Can optionally accept a second int parameter. The stat is then
39
+ # decremented by that amount.
40
+ def decr(stat, by = 1)
41
+ mongo_stats.update({ :stat => stat}, { '$inc' => { :value => -by}})
42
+ end
43
+
44
+ # Decrements a stat by one.
45
+ def >>(stat)
46
+ decr stat
47
+ end
48
+
49
+ # Removes a stat from Redis, effectively setting it to 0.
50
+ def clear(stat)
51
+ mongo_stats.remove({:stat => stat})
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,42 @@
1
+ # require 'resque/tasks'
2
+ # will give you the resque tasks
3
+
4
+ namespace :resque do
5
+ task :setup
6
+
7
+ desc "Start a Resque worker"
8
+ task :work => :setup do
9
+ require 'resque'
10
+
11
+ queues = (ENV['QUEUES'] || ENV['QUEUE']).to_s.split(',')
12
+
13
+ begin
14
+ worker = Resque::Worker.new(*queues)
15
+ worker.verbose = ENV['LOGGING'] || ENV['VERBOSE']
16
+ worker.very_verbose = ENV['VVERBOSE']
17
+ rescue Resque::NoQueueError
18
+ abort "set QUEUE env var, e.g. $ QUEUE=critical,high rake resque:work"
19
+ end
20
+
21
+ if ENV['PIDFILE']
22
+ File.open(ENV['PIDFILE'], 'w') { |f| f << worker.pid }
23
+ end
24
+
25
+ worker.log "Starting worker #{worker}"
26
+
27
+ worker.work(ENV['INTERVAL'] || 5) # interval, will block
28
+ end
29
+
30
+ desc "Start multiple Resque workers. Should only be used in dev mode."
31
+ task :workers do
32
+ threads = []
33
+
34
+ ENV['COUNT'].to_i.times do
35
+ threads << Thread.new do
36
+ system "rake resque:work"
37
+ end
38
+ end
39
+
40
+ threads.each { |thread| thread.join }
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module Resque
2
+ Version = VERSION = '1.17.1'
3
+ end
@@ -0,0 +1,497 @@
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
+ mongo_workers.distinct(:worker).map{|worker| find(worker)}.compact
28
+ end
29
+
30
+ # Returns an array of all worker objects currently processing
31
+ # jobs.
32
+ def self.working
33
+ working = mongo_workers.find({ 'working_on' => { '$exists' => true}}).to_a.map{|w| find(w['worker'])}
34
+ end
35
+
36
+ # Returns a single worker object. Accepts a string id.
37
+ def self.find(worker_id)
38
+ if exists? worker_id
39
+ queues = worker_id.split(':')[-1].split(',')
40
+ worker = new(*queues)
41
+ worker.to_s = worker_id
42
+ worker
43
+ else
44
+ nil
45
+ end
46
+ end
47
+
48
+ # Alias of `find`
49
+ def self.attach(worker_id)
50
+ find(worker_id)
51
+ end
52
+
53
+ # Given a string worker id, return a boolean indicating whether the
54
+ # worker exists
55
+ def self.exists?(worker_id)
56
+ mongo_workers.find({ :worker => worker_id.to_s}).count > 0
57
+ end
58
+
59
+ # Workers should be initialized with an array of string queue
60
+ # names. The order is important: a Worker will check the first
61
+ # queue given for a job. If none is found, it will check the
62
+ # second queue name given. If a job is found, it will be
63
+ # processed. Upon completion, the Worker will again check the
64
+ # first queue given, and so forth. In this way the queue list
65
+ # passed to a Worker on startup defines the priorities of queues.
66
+ #
67
+ # If passed a single "*", this Worker will operate on all queues
68
+ # in alphabetical order. Queues can be dynamically added or
69
+ # removed without needing to restart workers using this method.
70
+ def initialize(*queues)
71
+ @queues = queues.map { |queue| queue.to_s.strip }
72
+ validate_queues
73
+ end
74
+
75
+ # A worker must be given a queue, otherwise it won't know what to
76
+ # do with itself.
77
+ #
78
+ # You probably never need to call this.
79
+ def validate_queues
80
+ if @queues.nil? || @queues.empty?
81
+ raise NoQueueError.new("Please give each worker at least one queue.")
82
+ end
83
+ end
84
+
85
+ # This is the main workhorse method. Called on a Worker instance,
86
+ # it begins the worker life cycle.
87
+ #
88
+ # The following events occur during a worker's life cycle:
89
+ #
90
+ # 1. Startup: Signals are registered, dead workers are pruned,
91
+ # and this worker is registered.
92
+ # 2. Work loop: Jobs are pulled from a queue and processed.
93
+ # 3. Teardown: This worker is unregistered.
94
+ #
95
+ # Can be passed a float representing the polling frequency.
96
+ # The default is 5 seconds, but for a semi-active site you may
97
+ # want to use a smaller value.
98
+ #
99
+ # Also accepts a block which will be passed the job as soon as it
100
+ # has completed processing. Useful for testing.
101
+ def work(interval = 5.0, &block)
102
+ interval = Float(interval)
103
+ $0 = "resque: Starting"
104
+ startup
105
+
106
+ loop do
107
+ break if shutdown?
108
+
109
+ if not paused? and job = reserve
110
+ log "got: #{job.inspect}"
111
+ run_hook :before_fork, job
112
+ working_on job
113
+
114
+ if @child = fork
115
+ srand # Reseeding
116
+ procline "Forked #{@child} at #{Time.now.to_i}"
117
+ Process.wait
118
+ else
119
+ procline "Processing #{job.queue} since #{Time.now.to_i}"
120
+ perform(job, &block)
121
+ exit! unless @cant_fork
122
+ end
123
+
124
+ done_working
125
+ @child = nil
126
+ else
127
+ break if interval.zero?
128
+ log! "Sleeping for #{interval} seconds"
129
+ procline paused? ? "Paused" : "Waiting for #{@queues.join(',')}"
130
+ sleep interval
131
+ end
132
+ end
133
+
134
+ ensure
135
+ unregister_worker
136
+ end
137
+
138
+ # DEPRECATED. Processes a single job. If none is given, it will
139
+ # try to produce one. Usually run in the child.
140
+ def process(job = nil, &block)
141
+ return unless job ||= reserve
142
+
143
+ working_on job
144
+ perform(job, &block)
145
+ ensure
146
+ done_working
147
+ end
148
+
149
+ # Processes a given job in the child.
150
+ def perform(job)
151
+ begin
152
+ run_hook :after_fork, job
153
+ job.perform
154
+ rescue Object => e
155
+ log "#{job.inspect} failed: #{e.inspect}"
156
+ begin
157
+ job.fail(e)
158
+ rescue Object => e
159
+ log "Received exception when reporting failure: #{e.inspect}"
160
+ end
161
+ failed!
162
+ else
163
+ log "done: #{job.inspect}"
164
+ ensure
165
+ yield job if block_given?
166
+ end
167
+ end
168
+
169
+ # Attempts to grab a job off one of the provided queues. Returns
170
+ # nil if no job can be found.
171
+ def reserve
172
+ queues.each do |queue|
173
+ log! "Checking #{queue}"
174
+ if job = Resque::Job.reserve(queue)
175
+ log! "Found job on #{queue}"
176
+ return job
177
+ end
178
+ end
179
+
180
+ nil
181
+ rescue Exception => e
182
+ log "Error reserving job: #{e.inspect}"
183
+ log e.backtrace.join("\n")
184
+ raise e
185
+ end
186
+
187
+ # Returns a list of queues to use when searching for a job.
188
+ # A splat ("*") means you want every queue (in alpha order) - this
189
+ # can be useful for dynamically adding new queues.
190
+ def queues
191
+ @queues[0] == "*" ? Resque.queues.sort : @queues
192
+ end
193
+
194
+ # Not every platform supports fork. Here we do our magic to
195
+ # determine if yours does.
196
+ def fork
197
+ @cant_fork = true if $TESTING
198
+
199
+ return if @cant_fork
200
+
201
+ begin
202
+ # IronRuby doesn't support `Kernel.fork` yet
203
+ if Kernel.respond_to?(:fork)
204
+ Kernel.fork
205
+ else
206
+ raise NotImplementedError
207
+ end
208
+ rescue NotImplementedError
209
+ @cant_fork = true
210
+ nil
211
+ end
212
+ end
213
+
214
+ # Runs all the methods needed when a worker begins its lifecycle.
215
+ def startup
216
+ enable_gc_optimizations
217
+ register_signal_handlers
218
+ prune_dead_workers
219
+ run_hook :before_first_fork
220
+ register_worker
221
+
222
+ # Fix buffering so we can `rake resque:work > resque.log` and
223
+ # get output from the child in there.
224
+ $stdout.sync = true
225
+ end
226
+
227
+ # Enables GC Optimizations if you're running REE.
228
+ # http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
229
+ def enable_gc_optimizations
230
+ if GC.respond_to?(:copy_on_write_friendly=)
231
+ GC.copy_on_write_friendly = true
232
+ end
233
+ end
234
+
235
+ # Registers the various signal handlers a worker responds to.
236
+ #
237
+ # TERM: Shutdown immediately, stop processing jobs.
238
+ # INT: Shutdown immediately, stop processing jobs.
239
+ # QUIT: Shutdown after the current job has finished processing.
240
+ # USR1: Kill the forked child immediately, continue processing jobs.
241
+ # USR2: Don't process any new jobs
242
+ # CONT: Start processing jobs again after a USR2
243
+ def register_signal_handlers
244
+ trap('TERM') { shutdown! }
245
+ trap('INT') { shutdown! }
246
+
247
+ begin
248
+ trap('QUIT') { shutdown }
249
+ trap('USR1') { kill_child }
250
+ trap('USR2') { pause_processing }
251
+ trap('CONT') { unpause_processing }
252
+ rescue ArgumentError
253
+ warn "Signals QUIT, USR1, USR2, and/or CONT not supported."
254
+ end
255
+
256
+ log! "Registered signals"
257
+ end
258
+
259
+ # Schedule this worker for shutdown. Will finish processing the
260
+ # current job.
261
+ def shutdown
262
+ log 'Exiting...'
263
+ @shutdown = true
264
+ end
265
+
266
+ # Kill the child and shutdown immediately.
267
+ def shutdown!
268
+ shutdown
269
+ kill_child
270
+ end
271
+
272
+ # Should this worker shutdown as soon as current job is finished?
273
+ def shutdown?
274
+ @shutdown
275
+ end
276
+
277
+ # Kills the forked child immediately, without remorse. The job it
278
+ # is processing will not be completed.
279
+ def kill_child
280
+ if @child
281
+ log! "Killing child at #{@child}"
282
+ if system("ps -o pid,state -p #{@child}")
283
+ Process.kill("KILL", @child) rescue nil
284
+ else
285
+ log! "Child #{@child} not found, restarting."
286
+ shutdown
287
+ end
288
+ end
289
+ end
290
+
291
+ # are we paused?
292
+ def paused?
293
+ @paused
294
+ end
295
+
296
+ # Stop processing jobs after the current one has completed (if we're
297
+ # currently running one).
298
+ def pause_processing
299
+ log "USR2 received; pausing job processing"
300
+ @paused = true
301
+ end
302
+
303
+ # Start processing jobs again after a pause
304
+ def unpause_processing
305
+ log "CONT received; resuming job processing"
306
+ @paused = false
307
+ end
308
+
309
+ # Looks for any workers which should be running on this server
310
+ # and, if they're not, removes them from Mongo.
311
+ #
312
+ # This is a form of garbage collection. If a server is killed by a
313
+ # hard shutdown, power failure, or something else beyond our
314
+ # control, the Resque workers will not die gracefully and therefore
315
+ # will leave stale state information in Mongo.
316
+ #
317
+ # By checking the current Mongo state against the actual
318
+ # environment, we can determine if Mongo is old and clean it up a bit.
319
+ def prune_dead_workers
320
+ all_workers = Worker.all
321
+ known_workers = worker_pids unless all_workers.empty?
322
+ all_workers.each do |worker|
323
+ host, pid, queues = worker.id.split(':')
324
+ next unless host == hostname
325
+ next if known_workers.include?(pid)
326
+ log! "Pruning dead worker: #{worker}"
327
+ worker.unregister_worker
328
+ end
329
+ end
330
+
331
+ # Registers ourself as a worker. Useful when entering the worker
332
+ # lifecycle on startup.
333
+ def register_worker
334
+ mongo_workers << { :worker => self.to_s}
335
+ started!
336
+ end
337
+
338
+ # Runs a named hook, passing along any arguments.
339
+ def run_hook(name, *args)
340
+ return unless hook = Resque.send(name)
341
+ msg = "Running #{name} hook"
342
+ msg << " with #{args.inspect}" if args.any?
343
+ log msg
344
+
345
+ args.any? ? hook.call(*args) : hook.call
346
+ end
347
+
348
+ # Unregisters ourself as a worker. Useful when shutting down.
349
+ def unregister_worker
350
+ # If we're still processing a job, make sure it gets logged as a
351
+ # failure.
352
+ if (hash = processing) && !hash.empty?
353
+ job = Job.new(hash['queue'], hash['payload'])
354
+ # Ensure the proper worker is attached to this job, even if
355
+ # it's not the precise instance that died.
356
+ job.worker = self
357
+ job.fail(DirtyExit.new)
358
+ end
359
+
360
+ mongo_workers.remove :worker => self.to_s
361
+
362
+ Stat.clear("processed:#{self}")
363
+ Stat.clear("failed:#{self}")
364
+ end
365
+
366
+ # Given a job, tells Mongo we're working on it. Useful for seeing
367
+ # what workers are doing and when.
368
+ def working_on(job)
369
+ job.worker = self
370
+ data = #encode \
371
+ { :queue => job.queue,
372
+ :run_at => Time.now.to_s,
373
+ :payload => job.payload}
374
+ mongo_workers.update({:worker => self.to_s}, { '$set' => { 'working_on' => data}}, :upsert => true)
375
+ end
376
+
377
+ # Called when we are done working - clears our `working_on` state
378
+ # and tells Mongo we processed a job.
379
+ def done_working
380
+ processed!
381
+ mongo_workers.remove({ :worker => self.to_s})
382
+ end
383
+
384
+ # How many jobs has this worker processed? Returns an int.
385
+ def processed
386
+ Stat["processed:#{self}"]
387
+ end
388
+
389
+ # Tell Mongo we've processed a job.
390
+ def processed!
391
+ Stat << "processed"
392
+ Stat << "processed:#{self}"
393
+ end
394
+
395
+ # How many failed jobs has this worker seen? Returns an int.
396
+ def failed
397
+ Stat["failed:#{self}"]
398
+ end
399
+
400
+ # Tells Mongo we've failed a job.
401
+ def failed!
402
+ Stat << "failed"
403
+ Stat << "failed:#{self}"
404
+ end
405
+
406
+ # What time did this worker start? Returns an instance of `Time`
407
+ def started
408
+ worker = mongo_workers.find_one(:worker => self.to_s)
409
+ worker.nil? ? nil : worker['started']
410
+ end
411
+
412
+ # Tell Mongo we've started
413
+ def started!
414
+ mongo_workers.update({ :worker => self.to_s}, { '$set' => { :started => Time.now.to_s}})
415
+ end
416
+
417
+ # Returns a hash explaining the Job we're currently processing, if any.
418
+ def job
419
+ worker = mongo_workers.find_one :worker => self.to_s
420
+ worker.nil? ? { } : worker['working_on'] #decode(worker['working_on'])
421
+ end
422
+ alias_method :processing, :job
423
+
424
+ # Boolean - true if working, false if not
425
+ def working?
426
+ state == :working
427
+ end
428
+
429
+ # Boolean - true if idle, false if not
430
+ def idle?
431
+ state == :idle
432
+ end
433
+
434
+ # Returns a symbol representing the current worker state,
435
+ # which can be either :working or :idle
436
+ def state
437
+ mongo_workers.find_one(:worker => self.to_s) ? :working : :idle
438
+ end
439
+
440
+ # Is this worker the same as another worker?
441
+ def ==(other)
442
+ to_s == other.to_s
443
+ end
444
+
445
+ def inspect
446
+ "#<Worker #{to_s}>"
447
+ end
448
+
449
+ # The string representation is the same as the id for this worker
450
+ # instance. Can be used with `Worker.find`.
451
+ def to_s
452
+ @to_s ||= "#{hostname}:#{Process.pid}:#{@queues.join(',')}"
453
+ end
454
+ alias_method :id, :to_s
455
+
456
+ # chomp'd hostname of this machine
457
+ def hostname
458
+ @hostname ||= `hostname`.chomp
459
+ end
460
+
461
+ # Returns Integer PID of running worker
462
+ def pid
463
+ @pid ||= to_s.split(":")[1].to_i
464
+ end
465
+
466
+ # Returns an array of string pids of all the other workers on this
467
+ # machine. Useful when pruning dead workers on startup.
468
+ def worker_pids
469
+ `ps -A -o pid,command | grep [r]esque | grep -v "resque-web"`.split("\n").map do |line|
470
+ line.split(' ')[0]
471
+ end
472
+ end
473
+
474
+ # Given a string, sets the procline ($0) and logs.
475
+ # Procline is always in the format of:
476
+ # resque-VERSION: STRING
477
+ def procline(string)
478
+ $0 = "resque-#{Resque::Version}: #{string}"
479
+ log! $0
480
+ end
481
+
482
+ # Log a message to STDOUT if we are verbose or very_verbose.
483
+ def log(message)
484
+ if verbose
485
+ puts "*** #{message}"
486
+ elsif very_verbose
487
+ time = Time.now.strftime('%H:%M:%S %Y-%m-%d')
488
+ puts "** [#{time}] #$$: #{message}"
489
+ end
490
+ end
491
+
492
+ # Logs a very verbose message to STDOUT.
493
+ def log!(message)
494
+ log message if very_verbose
495
+ end
496
+ end
497
+ end