grockit-resque 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. data/.gitignore +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