resque-mongo 1.3.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 +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