grockit-resque 1.5.0

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