resque-mongo 1.3.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 (61) hide show
  1. data/.gitignore +3 -0
  2. data/.kick +26 -0
  3. data/CONTRIBUTORS +14 -0
  4. data/HISTORY.md +53 -0
  5. data/LICENSE +20 -0
  6. data/README.markdown +755 -0
  7. data/Rakefile +65 -0
  8. data/bin/resque +57 -0
  9. data/bin/resque-web +18 -0
  10. data/config.ru +14 -0
  11. data/deps.rip +8 -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 +27 -0
  16. data/examples/demo/config.ru +19 -0
  17. data/examples/demo/job.rb +12 -0
  18. data/examples/god/resque.god +52 -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.rb +224 -0
  24. data/lib/resque/errors.rb +7 -0
  25. data/lib/resque/failure.rb +63 -0
  26. data/lib/resque/failure/base.rb +58 -0
  27. data/lib/resque/failure/hoptoad.rb +88 -0
  28. data/lib/resque/failure/mongo.rb +32 -0
  29. data/lib/resque/helpers.rb +65 -0
  30. data/lib/resque/job.rb +103 -0
  31. data/lib/resque/server.rb +182 -0
  32. data/lib/resque/server/public/idle.png +0 -0
  33. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  34. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  35. data/lib/resque/server/public/poll.png +0 -0
  36. data/lib/resque/server/public/ranger.js +24 -0
  37. data/lib/resque/server/public/reset.css +48 -0
  38. data/lib/resque/server/public/style.css +75 -0
  39. data/lib/resque/server/public/working.png +0 -0
  40. data/lib/resque/server/views/error.erb +1 -0
  41. data/lib/resque/server/views/failed.erb +35 -0
  42. data/lib/resque/server/views/key.erb +17 -0
  43. data/lib/resque/server/views/layout.erb +38 -0
  44. data/lib/resque/server/views/next_more.erb +10 -0
  45. data/lib/resque/server/views/overview.erb +4 -0
  46. data/lib/resque/server/views/queues.erb +46 -0
  47. data/lib/resque/server/views/stats.erb +62 -0
  48. data/lib/resque/server/views/workers.erb +78 -0
  49. data/lib/resque/server/views/working.erb +67 -0
  50. data/lib/resque/stat.rb +55 -0
  51. data/lib/resque/tasks.rb +39 -0
  52. data/lib/resque/version.rb +3 -0
  53. data/lib/resque/worker.rb +415 -0
  54. data/tasks/redis.rake +133 -0
  55. data/tasks/resque.rake +2 -0
  56. data/test/dump.rdb +0 -0
  57. data/test/redis-test.conf +132 -0
  58. data/test/resque_test.rb +191 -0
  59. data/test/test_helper.rb +87 -0
  60. data/test/worker_test.rb +229 -0
  61. metadata +162 -0
@@ -0,0 +1,67 @@
1
+ <% if params[:id] && (worker = Resque::Worker.find(params[:id])) && worker.job %>
2
+ <h1><%= worker %>'s job</h1>
3
+
4
+ <table>
5
+ <tr>
6
+ <th>&nbsp;</th>
7
+ <th>Where</th>
8
+ <th>Queue</th>
9
+ <th>Started</th>
10
+ <th>Class</th>
11
+ <th>Args</th>
12
+ </tr>
13
+ <tr>
14
+ <td><img src="<%=u 'working.png' %>" alt="working" title="working"></td>
15
+ <% host, pid, _ = worker.to_s.split(':') %>
16
+ <td><a href="<%=u "/workers/#{worker}" %>"><%= host %>:<%= pid %></a></td>
17
+ <% data = worker.job %>
18
+ <% queue = data['queue'] %>
19
+ <td><a class="queue" href="<%=u "/queues/#{queue}" %>"><%= queue %></a></td>
20
+ <td><span class="time"><%= data['run_at'] %></span></td>
21
+ <td>
22
+ <code><%= data['payload']['class'] %></code>
23
+ </td>
24
+ <td><%=h data['payload']['args'].inspect %></td>
25
+ </tr>
26
+ </table>
27
+
28
+ <% else %>
29
+
30
+ <% workers = resque.working %>
31
+ <h1 class='wi'><%= workers.size %> of <%= resque.workers.size %> Workers Working</h1>
32
+ <p class='intro'>The list below contains all workers which are currently running a job.</p>
33
+ <table class='workers'>
34
+ <tr>
35
+ <th>&nbsp;</th>
36
+ <th>Where</th>
37
+ <th>Queue</th>
38
+ <th>Processing</th>
39
+ </tr>
40
+ <% if workers.empty? %>
41
+ <tr>
42
+ <td colspan="4" class='no-data'>Nothing is happening right now...</td>
43
+ </tr>
44
+ <% end %>
45
+
46
+ <% for worker in workers.sort_by { |w| w.job['run_at'] ? w.job['run_at'] : '' } %>
47
+ <% job = worker.job %>
48
+ <tr>
49
+ <td class='icon'><img src="<%=u state = worker.state %>.png" alt="<%= state %>" title="<%= state %>"></td>
50
+ <% host, pid, queues = worker.to_s.split(':') %>
51
+ <td class='where'><a href="<%=u "/workers/#{worker}" %>"><%= host %>:<%= pid %></a></td>
52
+ <td class='queues queue'>
53
+ <a class="queue-tag" href="<%=u "/queues/#{job['queue']}" %>"><%= job['queue'] %></a>
54
+ </td>
55
+ <td class='process'>
56
+ <% if job['queue'] %>
57
+ <code><%= job['payload']['class'] %></code>
58
+ <small><a class="queue time" href="<%=u "/working/#{worker}" %>"><%= job['run_at'] %></a></small>
59
+ <% else %>
60
+ <span class='waiting'>Waiting for a job...</span>
61
+ <% end %>
62
+ </td>
63
+ </tr>
64
+ <% end %>
65
+ </table>
66
+
67
+ <% end %>
@@ -0,0 +1,55 @@
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
+ res = mongo_stats.find_one(:stat => stat)
15
+ return 0 unless res
16
+ res['value'].to_i
17
+ end
18
+
19
+ # Alias of `get`
20
+ def [](stat)
21
+ get(stat)
22
+ end
23
+
24
+ # For a string stat name, increments the stat by one.
25
+ #
26
+ # Can optionally accept a second int parameter. The stat is then
27
+ # incremented by that amount.
28
+ def incr(stat, by = 1)
29
+ mongo_stats.update({:stat => stat}, {'$inc' => {:value => by}}, :upsert => true)
30
+ end
31
+
32
+ # Increments a stat by one.
33
+ def <<(stat)
34
+ incr stat
35
+ end
36
+
37
+ # For a string stat name, decrements the stat by one.
38
+ #
39
+ # Can optionally accept a second int parameter. The stat is then
40
+ # decremented by that amount.
41
+ def decr(stat, by = 1)
42
+ mongo_stats.update({:stat => stat}, {'$inc' => {:value => -by}})
43
+ end
44
+
45
+ # Decrements a stat by one.
46
+ def >>(stat)
47
+ decr stat
48
+ end
49
+
50
+ # Removes a stat from Redis, effectively setting it to 0.
51
+ def clear(stat)
52
+ mongo_stats.remove(:stat => stat)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,39 @@
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
+ worker = nil
12
+ queues = (ENV['QUEUES'] || ENV['QUEUE']).to_s.split(',')
13
+
14
+ begin
15
+ worker = Resque::Worker.new(*queues)
16
+ worker.verbose = ENV['LOGGING'] || ENV['VERBOSE']
17
+ worker.very_verbose = ENV['VVERBOSE']
18
+ rescue Resque::NoQueueError
19
+ abort "set QUEUE env var, e.g. $ QUEUE=critical,high rake resque:work"
20
+ end
21
+
22
+ puts "*** Starting worker #{worker}"
23
+
24
+ worker.work(ENV['INTERVAL'] || 5) # interval, will block
25
+ end
26
+
27
+ desc "Start multiple Resque workers"
28
+ task :workers do
29
+ threads = []
30
+
31
+ ENV['COUNT'].to_i.times do
32
+ threads << Thread.new do
33
+ system "rake resque:work"
34
+ end
35
+ end
36
+
37
+ threads.each { |thread| thread.join }
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ module Resque
2
+ Version = '1.3.1'
3
+ end
@@ -0,0 +1,415 @@
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) }
28
+ end
29
+
30
+ # Returns an array of all worker objects currently processing
31
+ # jobs.
32
+ def self.working
33
+ select = {}
34
+ select['working_on'] = {"$exists" => true}
35
+ working = mongo_workers.find(select).to_a
36
+ working.map! {|w| w['worker'] }
37
+ working.map {|w| find(w) }
38
+ end
39
+
40
+ # Returns a single worker object. Accepts a string id.
41
+ def self.find(worker_id)
42
+ worker = mongo_workers.find_one(:worker => worker_id)
43
+ return nil unless worker
44
+ queues = worker['worker'].split(',')
45
+ worker = new(*queues)
46
+ worker.to_s = worker_id
47
+ worker
48
+ end
49
+
50
+ # Alias of `find`
51
+ def self.attach(worker_id)
52
+ find(worker_id)
53
+ end
54
+
55
+ # # Given a string worker id, return a boolean indicating whether the
56
+ # # worker exists
57
+ def self.exists?(worker_id)
58
+ not mongo_workers.find_one(:worker => worker_id.to_s).nil?
59
+ end
60
+
61
+ # Workers should be initialized with an array of string queue
62
+ # names. The order is important: a Worker will check the first
63
+ # queue given for a job. If none is found, it will check the
64
+ # second queue name given. If a job is found, it will be
65
+ # processed. Upon completion, the Worker will again check the
66
+ # first queue given, and so forth. In this way the queue list
67
+ # passed to a Worker on startup defines the priorities of queues.
68
+ #
69
+ # If passed a single "*", this Worker will operate on all queues
70
+ # in alphabetical order. Queues can be dynamically added or
71
+ # removed without needing to restart workers using this method.
72
+ def initialize(*queues)
73
+ @queues = queues
74
+ validate_queues
75
+ end
76
+
77
+ # A worker must be given a queue, otherwise it won't know what to
78
+ # do with itself.
79
+ #
80
+ # You probably never need to call this.
81
+ def validate_queues
82
+ if @queues.nil? || @queues.empty?
83
+ raise NoQueueError.new("Please give each worker at least one queue.")
84
+ end
85
+ end
86
+
87
+ # This is the main workhorse method. Called on a Worker instance,
88
+ # it begins the worker life cycle.
89
+ #
90
+ # The following events occur during a worker's life cycle:
91
+ #
92
+ # 1. startup: Signals are registered, dead workers are pruned,
93
+ # and this worker is registered.
94
+ # 2. work loop: Jobs are pulled from a queue and processed
95
+ # 3. teardown: This worker is unregistered.
96
+ #
97
+ # Can be passed an integered representing the polling
98
+ # frequency. The default is 5 seconds, but for a semi-active site
99
+ # you may want to use a smaller value.
100
+ #
101
+ # Also accepts a block which will be passed the job as soon as it
102
+ # has completed processing. Useful for testing.
103
+ def work(interval = 5, &block)
104
+ $0 = "resque: Starting"
105
+ startup
106
+
107
+ loop do
108
+ break if @shutdown
109
+
110
+ if job = reserve
111
+ log "got: #{job.inspect}"
112
+
113
+ if @child = fork
114
+ rand # Reseeding
115
+ procline = "resque: Forked #{@child} at #{Time.now.to_i}"
116
+ $0 = procline
117
+ log! procline
118
+ Process.wait
119
+ else
120
+ procline = "resque: Processing #{job.queue} since #{Time.now.to_i}"
121
+ $0 = procline
122
+ log! procline
123
+ process(job, &block)
124
+ exit! unless @cant_fork
125
+ end
126
+
127
+ @child = nil
128
+ else
129
+ break if interval.to_i == 0
130
+ log! "Sleeping for #{interval.to_i}"
131
+ $0 = "resque: Waiting for #{@queues.join(',')}"
132
+ sleep interval.to_i
133
+ end
134
+ end
135
+
136
+ ensure
137
+ unregister_worker
138
+ end
139
+
140
+ # Processes a single job. If none is given, it will try to produce
141
+ # one.
142
+ def process(job = nil)
143
+ return unless job ||= reserve
144
+
145
+ begin
146
+ working_on job
147
+ job.perform
148
+ rescue Object => e
149
+ log "#{job.inspect} failed: #{e.inspect}"
150
+ job.fail(e)
151
+ failed!
152
+ else
153
+ log "done: #{job.inspect}"
154
+ ensure
155
+ yield job if block_given?
156
+ done_working
157
+ end
158
+ end
159
+
160
+ # Attempts to grab a job off one of the provided queues. Returns
161
+ # nil if no job can be found.
162
+ def reserve
163
+ queues.each do |queue|
164
+ log! "Checking #{queue}"
165
+ if job = Resque::Job.reserve(queue)
166
+ log! "Found job on #{queue}"
167
+ return job
168
+ end
169
+ end
170
+
171
+ nil
172
+ end
173
+
174
+ # Returns a list of queues to use when searching for a job.
175
+ # A splat ("*") means you want every queue (in alpha order) - this
176
+ # can be useful for dynamically adding new queues.
177
+ def queues
178
+ @queues[0] == "*" ? Resque.queues.sort : @queues
179
+ end
180
+
181
+ # Not every platform supports fork. Here we do our magic to
182
+ # determine if yours does.
183
+ def fork
184
+ @cant_fork = true if $TESTING
185
+
186
+ return if @cant_fork
187
+
188
+ begin
189
+ Kernel.fork
190
+ rescue NotImplementedError
191
+ @cant_fork = true
192
+ nil
193
+ end
194
+ end
195
+
196
+ # Runs all the methods needed when a worker begins its lifecycle.
197
+ def startup
198
+ enable_gc_optimizations
199
+ register_signal_handlers
200
+ prune_dead_workers
201
+ register_worker
202
+ end
203
+
204
+ # Enables GC Optimizations if you're running REE.
205
+ # http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
206
+ def enable_gc_optimizations
207
+ if GC.respond_to?(:copy_on_write_friendly=)
208
+ GC.copy_on_write_friendly = true
209
+ end
210
+ end
211
+
212
+ # Registers the various signal handlers a worker responds to.
213
+ #
214
+ # TERM: Shutdown immediately, stop processing jobs.
215
+ # INT: Shutdown immediately, stop processing jobs.
216
+ # QUIT: Shutdown after the current job has finished processing.
217
+ # USR1: Kill the forked child immediately, continue processing jobs.
218
+ def register_signal_handlers
219
+ trap('TERM') { shutdown! }
220
+ trap('INT') { shutdown! }
221
+ unless defined? JRUBY_VERSION
222
+ trap('QUIT') { shutdown }
223
+ trap('USR1') { kill_child }
224
+ end
225
+ log! "Registered signals"
226
+ end
227
+
228
+ # Schedule this worker for shutdown. Will finish processing the
229
+ # current job.
230
+ def shutdown
231
+ log 'Exiting...'
232
+ @shutdown = true
233
+ end
234
+
235
+ # Kill the child and shutdown immediately.
236
+ def shutdown!
237
+ shutdown
238
+ kill_child
239
+ end
240
+
241
+ # Kills the forked child immediately, without remorse. The job it
242
+ # is processing will not be completed.
243
+ def kill_child
244
+ if @child
245
+ log! "Killing child at #{@child}"
246
+ if system("ps -o pid,state -p #{@child}")
247
+ Process.kill("KILL", @child) rescue nil
248
+ else
249
+ log! "Child #{@child} not found, restarting."
250
+ shutdown
251
+ end
252
+ end
253
+ end
254
+
255
+ # Looks for any workers which should be running on this server
256
+ # and, if they're not, removes them from Redis.
257
+ #
258
+ # This is a form of garbage collection. If a server is killed by a
259
+ # hard shutdown, power failure, or something else beyond our
260
+ # control, the Resque workers will not die gracefully and therefor
261
+ # will leave stale state information in Redis.
262
+ #
263
+ # By checking the current Redis state against the actual
264
+ # environment, we can determine if Redis is old and clean it up a bit.
265
+ def prune_dead_workers
266
+ Worker.all.each do |worker|
267
+ host, pid, queues = worker.to_s.split(':')
268
+ next unless host == hostname
269
+ next if worker_pids.include?(pid)
270
+ log! "Pruning dead worker: #{worker}"
271
+ worker.unregister_worker
272
+ end
273
+ end
274
+
275
+ # Registers ourself as a worker. Useful when entering the worker
276
+ # lifecycle on startup.
277
+ def register_worker
278
+ mongo_workers.insert(:worker => self.to_s)
279
+ started!
280
+ end
281
+
282
+ # Unregisters ourself as a worker. Useful when shutting down.
283
+ def unregister_worker
284
+ done_working
285
+
286
+ mongo_workers.remove(:worker => self.to_s)
287
+ Stat.clear("processed:#{self}")
288
+ Stat.clear("failed:#{self}")
289
+ end
290
+
291
+ # Given a job, tells Redis we're working on it. Useful for seeing
292
+ # what workers are doing and when.
293
+ def working_on(job)
294
+ job.worker = self.to_s
295
+ data = encode \
296
+ :queue => job.queue,
297
+ :run_at => Time.now.to_s,
298
+ :payload => job.payload
299
+ working_on = {'working_on' => data}
300
+ mongo_workers.update({:worker => self.to_s}, {'$set' => working_on})
301
+ end
302
+
303
+ # Called when we are done working - clears our `working_on` state
304
+ # and tells Redis we processed a job.
305
+ def done_working
306
+ processed!
307
+ working_on = {'working_on' => 1}
308
+ mongo_workers.update({:worker => self.to_s}, {'$unset' => working_on})
309
+ end
310
+
311
+ # How many jobs has this worker processed? Returns an int.
312
+ def processed
313
+ Stat["processed:#{self}"]
314
+ end
315
+
316
+ # Tell Redis we've processed a job.
317
+ def processed!
318
+ Stat << "processed"
319
+ Stat << "processed:#{self}"
320
+ end
321
+
322
+ # How many failed jobs has this worker seen? Returns an int.
323
+ def failed
324
+ Stat["failed:#{self}"]
325
+ end
326
+
327
+ # Tells Redis we've failed a job.
328
+ def failed!
329
+ Stat << "failed"
330
+ Stat << "failed:#{self}"
331
+ end
332
+
333
+ # What time did this worker start? Returns an instance of `Time`
334
+ def started
335
+ worker = mongo_workers.find_one(:worker => self.to_s)
336
+ return nil if !worker
337
+ worker['started']
338
+ end
339
+
340
+ # Tell Redis we've started
341
+ def started!
342
+ started = {'started' => Time.now.to_s}
343
+ mongo_workers.update({:worker => self.to_s}, {'$set' => started})
344
+ end
345
+
346
+ # Returns a hash explaining the Job we're currently processing, if any.
347
+ def job
348
+ worker = mongo_workers.find_one(:worker => self.to_s)
349
+ return {} if !worker
350
+ decode(worker['working_on']) || {}
351
+ end
352
+ alias_method :processing, :job
353
+
354
+ # Boolean - true if working, false if not
355
+ def working?
356
+ state == :working
357
+ end
358
+
359
+ # Boolean - true if idle, false if not
360
+ def idle?
361
+ state == :idle
362
+ end
363
+
364
+ # Returns a symbol representing the current worker state,
365
+ # which can be either :working or :idle
366
+ def state
367
+ worker = mongo_workers.find_one(:worker => self.to_s)
368
+ worker ? :working : :idle
369
+ end
370
+
371
+ # Is this worker the same as another worker?
372
+ def ==(other)
373
+ to_s == other.to_s
374
+ end
375
+
376
+ def inspect
377
+ "#<Worker #{to_s}>"
378
+ end
379
+
380
+ # The string representation is the same as the id for this worker
381
+ # instance. Can be used with `Worker.find`.
382
+ def to_s
383
+ @to_s ||= "#{hostname}:#{Process.pid}:#{@queues.join(',')}"
384
+ end
385
+ alias_method :worker_id, :to_s
386
+
387
+ # chomp'd hostname of this machine
388
+ def hostname
389
+ @hostname ||= `hostname`.chomp
390
+ end
391
+
392
+ # Returns an array of string pids of all the other workers on this
393
+ # machine. Useful when pruning dead workers on startup.
394
+ def worker_pids
395
+ `ps -A -o pid,command | grep [r]esque`.split("\n").map do |line|
396
+ line.split(' ')[0]
397
+ end
398
+ end
399
+
400
+ # Log a message to STDOUT if we are verbose or very_verbose.
401
+ def log(message)
402
+ if verbose
403
+ puts "*** #{message}"
404
+ elsif very_verbose
405
+ time = Time.now.strftime('%I:%M:%S %Y-%m-%d')
406
+ puts "** [#{time}] #$$: #{message}"
407
+ end
408
+ end
409
+
410
+ # Logs a very verbose message to STDOUT.
411
+ def log!(message)
412
+ log message if very_verbose
413
+ end
414
+ end
415
+ end