resque-igo 1.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 (50) hide show
  1. data/HISTORY.md +225 -0
  2. data/LICENSE +20 -0
  3. data/README.markdown +855 -0
  4. data/Rakefile +70 -0
  5. data/bin/resque +57 -0
  6. data/bin/resque-web +23 -0
  7. data/lib/resque.rb +380 -0
  8. data/lib/resque/errors.rb +10 -0
  9. data/lib/resque/failure.rb +66 -0
  10. data/lib/resque/failure/base.rb +61 -0
  11. data/lib/resque/failure/hoptoad.rb +132 -0
  12. data/lib/resque/failure/multiple.rb +50 -0
  13. data/lib/resque/failure/redis.rb +40 -0
  14. data/lib/resque/helpers.rb +71 -0
  15. data/lib/resque/job.rb +209 -0
  16. data/lib/resque/plugin.rb +51 -0
  17. data/lib/resque/server.rb +247 -0
  18. data/lib/resque/server/public/idle.png +0 -0
  19. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  20. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  21. data/lib/resque/server/public/poll.png +0 -0
  22. data/lib/resque/server/public/ranger.js +67 -0
  23. data/lib/resque/server/public/reset.css +48 -0
  24. data/lib/resque/server/public/style.css +86 -0
  25. data/lib/resque/server/public/working.png +0 -0
  26. data/lib/resque/server/test_helper.rb +19 -0
  27. data/lib/resque/server/views/error.erb +1 -0
  28. data/lib/resque/server/views/failed.erb +53 -0
  29. data/lib/resque/server/views/key_sets.erb +20 -0
  30. data/lib/resque/server/views/key_string.erb +11 -0
  31. data/lib/resque/server/views/layout.erb +42 -0
  32. data/lib/resque/server/views/next_more.erb +10 -0
  33. data/lib/resque/server/views/overview.erb +4 -0
  34. data/lib/resque/server/views/queues.erb +65 -0
  35. data/lib/resque/server/views/stats.erb +73 -0
  36. data/lib/resque/server/views/workers.erb +109 -0
  37. data/lib/resque/server/views/working.erb +68 -0
  38. data/lib/resque/stat.rb +54 -0
  39. data/lib/resque/tasks.rb +39 -0
  40. data/lib/resque/version.rb +3 -0
  41. data/lib/resque/worker.rb +478 -0
  42. data/tasks/resque.rake +2 -0
  43. data/test/job_hooks_test.rb +323 -0
  44. data/test/job_plugins_test.rb +230 -0
  45. data/test/plugin_test.rb +116 -0
  46. data/test/resque-web_test.rb +54 -0
  47. data/test/resque_test.rb +351 -0
  48. data/test/test_helper.rb +166 -0
  49. data/test/worker_test.rb +302 -0
  50. metadata +180 -0
@@ -0,0 +1,109 @@
1
+ <% @subtabs = worker_hosts.keys.sort unless worker_hosts.size == 1 %>
2
+
3
+ <% if params[:id] && worker = Resque::Worker.find(params[:id]) %>
4
+
5
+ <h1>Worker <%= worker %></h1>
6
+ <table class='workers'>
7
+ <tr>
8
+ <th>&nbsp;</th>
9
+ <th>Host</th>
10
+ <th>Pid</th>
11
+ <th>Started</th>
12
+ <th>Queues</th>
13
+ <th>Processed</th>
14
+ <th>Failed</th>
15
+ <th>Processing</th>
16
+ </tr>
17
+ <tr>
18
+ <td class='icon'><img src="<%=u state = worker.state %>.png" alt="<%= state %>" title="<%= state %>"></td>
19
+
20
+ <% host, pid, queues = worker.to_s.split(':') %>
21
+ <td><%= host %></td>
22
+ <td><%= pid %></td>
23
+ <td><span class="time"><%= worker.started %></span></td>
24
+ <td class='queues'><%= queues.split(',').map { |q| '<a class="queue-tag" href="' + u("/queues/#{q}") + '">' + q + '</a>'}.join('') %></td>
25
+ <td><%= worker.processed %></td>
26
+ <td><%= worker.failed %></td>
27
+ <td class='process'>
28
+ <% data = worker.processing || {} %>
29
+ <% if data['queue'] %>
30
+ <code><%= data['payload']['class'] %></code>
31
+ <small><a class="queue time" href="<%=u "/working/#{worker}" %>"><%= data['run_at'] %></a></small>
32
+ <% else %>
33
+ <span class='waiting'>Waiting for a job...</span>
34
+ <% end %>
35
+ </td>
36
+ </tr>
37
+ </table>
38
+
39
+ <% elsif params[:id] && !worker_hosts.keys.include?(params[:id]) && params[:id] != 'all' %>
40
+
41
+ <h1>Worker doesn't exist</h1>
42
+
43
+ <% elsif worker_hosts.size == 1 || params[:id] %>
44
+
45
+ <% if worker_hosts.size == 1 || params[:id] == 'all' %>
46
+ <% workers = Resque.workers %>
47
+ <% else %>
48
+ <% workers = worker_hosts[params[:id]].map { |id| Resque::Worker.find(id) } %>
49
+ <% end %>
50
+
51
+ <h1 class='wi'><%= workers.size %> Workers</h1>
52
+ <p class='intro'>The workers listed below are all registered as active on your system.</p>
53
+ <table class='workers'>
54
+ <tr>
55
+ <th>&nbsp;</th>
56
+ <th>Where</th>
57
+ <th>Queues</th>
58
+ <th>Processing</th>
59
+ </tr>
60
+ <% for worker in (workers = workers.sort_by { |w| w.to_s }) %>
61
+ <tr class="<%=state = worker.state%>">
62
+ <td class='icon'><img src="<%=u state %>.png" alt="<%= state %>" title="<%= state %>"></td>
63
+
64
+ <% host, pid, queues = worker.to_s.split(':') %>
65
+ <td class='where'><a href="<%=u "workers/#{worker}"%>"><%= host %>:<%= pid %></a></td>
66
+ <td class='queues'><%= queues.split(',').map { |q| '<a class="queue-tag" href="' + u("/queues/#{q}") + '">' + q + '</a>'}.join('') %></td>
67
+
68
+ <td class='process'>
69
+ <% data = worker.processing || {} %>
70
+ <% if data['queue'] %>
71
+ <code><%= data['payload']['class'] %></code>
72
+ <small><a class="queue time" href="<%=u "/working/#{worker}" %>"><%= data['run_at'] %></a></small>
73
+ <% else %>
74
+ <span class='waiting'>Waiting for a job...</span>
75
+ <% end %>
76
+ </td>
77
+ </tr>
78
+ <% end %>
79
+ <% if workers.empty? %>
80
+ <tr>
81
+ <td colspan='4' class='no-data'>There are no registered workers</td>
82
+ </tr>
83
+ <% end %>
84
+ </table>
85
+ <%=poll%>
86
+
87
+ <% else %>
88
+ <% @subtabs = [] %>
89
+ <h1 class='wi'>Workers</h1>
90
+ <p class='intro'>The hostnames below all have registered workers. Select a hostname to view its workers, or "all" to see all workers.</p>
91
+ <table class='queues'>
92
+ <tr>
93
+ <th>Hostname</th>
94
+ <th>Workers</th>
95
+ </tr>
96
+ <% for hostname, workers in worker_hosts.sort_by { |h,w| h } %>
97
+ <tr>
98
+ <td class='queue'><a class="queue" href="<%= url "workers/#{hostname}" %>"><%= hostname %></a></td>
99
+ <td class='size'><%= workers.size %></td>
100
+ </tr>
101
+ <% end %>
102
+ <tr class="failed">
103
+ <td class='queue failed'><a class="queue" href="<%= url "workers/all" %>">all workers</a></td>
104
+ <td class='size'><%= Resque.workers.size %></td>
105
+ </tr>
106
+ </table>
107
+
108
+
109
+ <% end %>
@@ -0,0 +1,68 @@
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.reject { |w| w.idle? } %>
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
+
49
+ <tr>
50
+ <td class='icon'><img src="<%=u state = worker.state %>.png" alt="<%= state %>" title="<%= state %>"></td>
51
+ <% host, pid, queues = worker.to_s.split(':') %>
52
+ <td class='where'><a href="<%=u "/workers/#{worker}" %>"><%= host %>:<%= pid %></a></td>
53
+ <td class='queues queue'>
54
+ <a class="queue-tag" href="<%=u "/queues/#{job['queue']}" %>"><%= job['queue'] %></a>
55
+ </td>
56
+ <td class='process'>
57
+ <% if job['queue'] %>
58
+ <code><%= job['payload']['class'] %></code>
59
+ <small><a class="queue time" href="<%=u "/working/#{worker}" %>"><%= job['run_at'] %></a></small>
60
+ <% else %>
61
+ <span class='waiting'>Waiting for a job...</span>
62
+ <% end %>
63
+ </td>
64
+ </tr>
65
+ <% end %>
66
+ </table>
67
+
68
+ <% end %>
@@ -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,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
+ worker.log "Starting worker #{worker}"
23
+
24
+ worker.work(ENV['INTERVAL'] || 5) # interval, will block
25
+ end
26
+
27
+ desc "Start multiple Resque workers. Should only be used in dev mode."
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 = VERSION = '1.1'
3
+ end
@@ -0,0 +1,478 @@
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
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 an integer 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, &block)
102
+ $0 = "resque: Starting"
103
+ startup
104
+
105
+ loop do
106
+ break if shutdown?
107
+
108
+ if not @paused and job = reserve
109
+ log "got: #{job.inspect}"
110
+ run_hook :before_fork, job
111
+ working_on job
112
+
113
+ if @child = fork
114
+ rand # Reseeding
115
+ procline "Forked #{@child} at #{Time.now.to_i}"
116
+ Process.wait
117
+ else
118
+ procline "Processing #{job.queue} since #{Time.now.to_i}"
119
+ perform(job, &block)
120
+ exit! unless @cant_fork
121
+ end
122
+
123
+ done_working
124
+ @child = nil
125
+ else
126
+ break if interval.to_i == 0
127
+ log! "Sleeping for #{interval.to_i}"
128
+ procline @paused ? "Paused" : "Waiting for #{@queues.join(',')}"
129
+ sleep interval.to_i
130
+ end
131
+ end
132
+
133
+ ensure
134
+ unregister_worker
135
+ end
136
+
137
+ # DEPRECATED. Processes a single job. If none is given, it will
138
+ # try to produce one. Usually run in the child.
139
+ def process(job = nil, &block)
140
+ return unless job ||= reserve
141
+
142
+ working_on job
143
+ perform(job, &block)
144
+ ensure
145
+ done_working
146
+ end
147
+
148
+ # Processes a given job in the child.
149
+ def perform(job)
150
+ begin
151
+ run_hook :after_fork, job
152
+ job.perform
153
+ rescue Object => e
154
+ log "#{job.inspect} failed: #{e.inspect}"
155
+ job.fail(e)
156
+ failed!
157
+ else
158
+ log "done: #{job.inspect}"
159
+ ensure
160
+ yield job if block_given?
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
+ run_hook :before_first_fork
211
+ register_worker
212
+
213
+ # Fix buffering so we can `rake resque:work > resque.log` and
214
+ # get output from the child in there.
215
+ $stdout.sync = true
216
+ end
217
+
218
+ # Enables GC Optimizations if you're running REE.
219
+ # http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
220
+ def enable_gc_optimizations
221
+ if GC.respond_to?(:copy_on_write_friendly=)
222
+ GC.copy_on_write_friendly = true
223
+ end
224
+ end
225
+
226
+ # Registers the various signal handlers a worker responds to.
227
+ #
228
+ # TERM: Shutdown immediately, stop processing jobs.
229
+ # INT: Shutdown immediately, stop processing jobs.
230
+ # QUIT: Shutdown after the current job has finished processing.
231
+ # USR1: Kill the forked child immediately, continue processing jobs.
232
+ # USR2: Don't process any new jobs
233
+ # CONT: Start processing jobs again after a USR2
234
+ def register_signal_handlers
235
+ trap('TERM') { shutdown! }
236
+ trap('INT') { shutdown! }
237
+
238
+ begin
239
+ trap('QUIT') { shutdown }
240
+ trap('USR1') { kill_child }
241
+ trap('USR2') { pause_processing }
242
+ trap('CONT') { unpause_processing }
243
+ rescue ArgumentError
244
+ warn "Signals QUIT, USR1, USR2, and/or CONT not supported."
245
+ end
246
+
247
+ log! "Registered signals"
248
+ end
249
+
250
+ # Schedule this worker for shutdown. Will finish processing the
251
+ # current job.
252
+ def shutdown
253
+ log 'Exiting...'
254
+ @shutdown = true
255
+ end
256
+
257
+ # Kill the child and shutdown immediately.
258
+ def shutdown!
259
+ shutdown
260
+ kill_child
261
+ end
262
+
263
+ # Should this worker shutdown as soon as current job is finished?
264
+ def shutdown?
265
+ @shutdown
266
+ end
267
+
268
+ # Kills the forked child immediately, without remorse. The job it
269
+ # is processing will not be completed.
270
+ def kill_child
271
+ if @child
272
+ log! "Killing child at #{@child}"
273
+ if system("ps -o pid,state -p #{@child}")
274
+ Process.kill("KILL", @child) rescue nil
275
+ else
276
+ log! "Child #{@child} not found, restarting."
277
+ shutdown
278
+ end
279
+ end
280
+ end
281
+
282
+ # Stop processing jobs after the current one has completed (if we're
283
+ # currently running one).
284
+ def pause_processing
285
+ log "USR2 received; pausing job processing"
286
+ @paused = true
287
+ end
288
+
289
+ # Start processing jobs again after a pause
290
+ def unpause_processing
291
+ log "CONT received; resuming job processing"
292
+ @paused = false
293
+ end
294
+
295
+ # Looks for any workers which should be running on this server
296
+ # and, if they're not, removes them from Mongo.
297
+ #
298
+ # This is a form of garbage collection. If a server is killed by a
299
+ # hard shutdown, power failure, or something else beyond our
300
+ # control, the Resque workers will not die gracefully and therefore
301
+ # will leave stale state information in Mongo.
302
+ #
303
+ # By checking the current Mongo state against the actual
304
+ # environment, we can determine if Mongo is old and clean it up a bit.
305
+ def prune_dead_workers
306
+ all_workers = Worker.all
307
+ known_workers = worker_pids unless all_workers.empty?
308
+ all_workers.each do |worker|
309
+ host, pid, queues = worker.id.split(':')
310
+ next unless host == hostname
311
+ next if known_workers.include?(pid)
312
+ log! "Pruning dead worker: #{worker}"
313
+ worker.unregister_worker
314
+ end
315
+ end
316
+
317
+ # Registers ourself as a worker. Useful when entering the worker
318
+ # lifecycle on startup.
319
+ def register_worker
320
+ mongo_workers << { :worker => self.to_s}
321
+ started!
322
+ end
323
+
324
+ # Runs a named hook, passing along any arguments.
325
+ def run_hook(name, *args)
326
+ return unless hook = Resque.send(name)
327
+ msg = "Running #{name} hook"
328
+ msg << " with #{args.inspect}" if args.any?
329
+ log msg
330
+
331
+ args.any? ? hook.call(*args) : hook.call
332
+ end
333
+
334
+ # Unregisters ourself as a worker. Useful when shutting down.
335
+ def unregister_worker
336
+ # If we're still processing a job, make sure it gets logged as a
337
+ # failure.
338
+ if (hash = processing) && !hash.empty?
339
+ job = Job.new(hash['queue'], hash['payload'])
340
+ # Ensure the proper worker is attached to this job, even if
341
+ # it's not the precise instance that died.
342
+ job.worker = self
343
+ job.fail(DirtyExit.new)
344
+ end
345
+
346
+ mongo_workers.remove :worker => self.to_s
347
+
348
+ Stat.clear("processed:#{self}")
349
+ Stat.clear("failed:#{self}")
350
+ end
351
+
352
+ # Given a job, tells Mongo we're working on it. Useful for seeing
353
+ # what workers are doing and when.
354
+ def working_on(job)
355
+ job.worker = self
356
+ data = #encode \
357
+ { :queue => job.queue,
358
+ :run_at => Time.now.to_s,
359
+ :payload => job.payload}
360
+ mongo_workers.update({:worker => self.to_s}, { '$set' => { 'working_on' => data}}, :upsert => true)
361
+ end
362
+
363
+ # Called when we are done working - clears our `working_on` state
364
+ # and tells Mongo we processed a job.
365
+ def done_working
366
+ processed!
367
+ mongo_workers.remove({ :worker => self.to_s})
368
+ end
369
+
370
+ # How many jobs has this worker processed? Returns an int.
371
+ def processed
372
+ Stat["processed:#{self}"]
373
+ end
374
+
375
+ # Tell Mongo we've processed a job.
376
+ def processed!
377
+ Stat << "processed"
378
+ Stat << "processed:#{self}"
379
+ end
380
+
381
+ # How many failed jobs has this worker seen? Returns an int.
382
+ def failed
383
+ Stat["failed:#{self}"]
384
+ end
385
+
386
+ # Tells Mongo we've failed a job.
387
+ def failed!
388
+ Stat << "failed"
389
+ Stat << "failed:#{self}"
390
+ end
391
+
392
+ # What time did this worker start? Returns an instance of `Time`
393
+ def started
394
+ worker = mongo_workers.find_one(:worker => self.to_s)
395
+ worker.nil? ? nil : worker['started']
396
+ end
397
+
398
+ # Tell Mongo we've started
399
+ def started!
400
+ mongo_workers.update({ :worker => self.to_s}, { '$set' => { :started => Time.now.to_s}})
401
+ end
402
+
403
+ # Returns a hash explaining the Job we're currently processing, if any.
404
+ def job
405
+ worker = mongo_workers.find_one :worker => self.to_s
406
+ worker.nil? ? { } : worker['working_on'] #decode(worker['working_on'])
407
+ end
408
+ alias_method :processing, :job
409
+
410
+ # Boolean - true if working, false if not
411
+ def working?
412
+ state == :working
413
+ end
414
+
415
+ # Boolean - true if idle, false if not
416
+ def idle?
417
+ state == :idle
418
+ end
419
+
420
+ # Returns a symbol representing the current worker state,
421
+ # which can be either :working or :idle
422
+ def state
423
+ mongo_workers.find_one(:worker => self.to_s) ? :working : :idle
424
+ end
425
+
426
+ # Is this worker the same as another worker?
427
+ def ==(other)
428
+ to_s == other.to_s
429
+ end
430
+
431
+ def inspect
432
+ "#<Worker #{to_s}>"
433
+ end
434
+
435
+ # The string representation is the same as the id for this worker
436
+ # instance. Can be used with `Worker.find`.
437
+ def to_s
438
+ @to_s ||= "#{hostname}:#{Process.pid}:#{@queues.join(',')}"
439
+ end
440
+ alias_method :id, :to_s
441
+
442
+ # chomp'd hostname of this machine
443
+ def hostname
444
+ @hostname ||= `hostname`.chomp
445
+ end
446
+
447
+ # Returns an array of string pids of all the other workers on this
448
+ # machine. Useful when pruning dead workers on startup.
449
+ def worker_pids
450
+ `ps -A -o pid,command | grep [r]esque`.split("\n").map do |line|
451
+ line.split(' ')[0]
452
+ end
453
+ end
454
+
455
+ # Given a string, sets the procline ($0) and logs.
456
+ # Procline is always in the format of:
457
+ # resque-VERSION: STRING
458
+ def procline(string)
459
+ $0 = "resque-#{Resque::Version}: #{string}"
460
+ log! $0
461
+ end
462
+
463
+ # Log a message to STDOUT if we are verbose or very_verbose.
464
+ def log(message)
465
+ if verbose
466
+ puts "*** #{message}"
467
+ elsif very_verbose
468
+ time = Time.now.strftime('%I:%M:%S %Y-%m-%d')
469
+ puts "** [#{time}] #$$: #{message}"
470
+ end
471
+ end
472
+
473
+ # Logs a very verbose message to STDOUT.
474
+ def log!(message)
475
+ log message if very_verbose
476
+ end
477
+ end
478
+ end