resque-igo 1.1

Sign up to get free protection for your applications and to get access to all the features.
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