resque-mongo 1.4.0 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/CONTRIBUTORS +24 -6
  2. data/HISTORY.md +65 -0
  3. data/README.markdown +34 -5
  4. data/Rakefile +1 -1
  5. data/bin/resque +2 -2
  6. data/bin/resque-web +6 -1
  7. data/deps.rip +2 -2
  8. data/docs/HOOKS.md +121 -0
  9. data/docs/PLUGINS.md +93 -0
  10. data/examples/demo/Rakefile +5 -0
  11. data/examples/monit/resque.monit +6 -0
  12. data/lib/resque.rb +94 -7
  13. data/lib/resque/errors.rb +3 -0
  14. data/lib/resque/failure.rb +3 -0
  15. data/lib/resque/failure/base.rb +3 -0
  16. data/lib/resque/failure/hoptoad.rb +29 -19
  17. data/lib/resque/failure/mongo.rb +10 -1
  18. data/lib/resque/helpers.rb +8 -2
  19. data/lib/resque/job.rb +107 -2
  20. data/lib/resque/plugin.rb +46 -0
  21. data/lib/resque/server.rb +30 -11
  22. data/lib/resque/server/public/ranger.js +50 -7
  23. data/lib/resque/server/public/style.css +8 -1
  24. data/lib/resque/server/test_helper.rb +19 -0
  25. data/lib/resque/server/views/failed.erb +17 -3
  26. data/lib/resque/server/views/key_sets.erb +20 -0
  27. data/lib/resque/server/views/{key.erb → key_string.erb} +2 -8
  28. data/lib/resque/server/views/queues.erb +5 -2
  29. data/lib/resque/server/views/stats.erb +2 -2
  30. data/lib/resque/server/views/workers.erb +1 -1
  31. data/lib/resque/server/views/working.erb +2 -0
  32. data/lib/resque/tasks.rb +1 -1
  33. data/lib/resque/version.rb +1 -1
  34. data/lib/resque/worker.rb +54 -15
  35. data/tasks/redis.rake +53 -29
  36. data/test/job_hooks_test.rb +302 -0
  37. data/test/job_plugins_test.rb +209 -0
  38. data/test/plugin_test.rb +116 -0
  39. data/test/resque-mongo_benchmark.rb +62 -0
  40. data/test/resque-web_test.rb +54 -0
  41. data/test/resque_test.rb +34 -0
  42. data/test/test_helper.rb +15 -0
  43. data/test/worker_test.rb +62 -2
  44. metadata +58 -23
@@ -109,26 +109,25 @@ module Resque
109
109
 
110
110
  if not @paused and job = reserve
111
111
  log "got: #{job.inspect}"
112
+ run_hook :before_fork
113
+ working_on job
112
114
 
113
115
  if @child = fork
114
116
  rand # Reseeding
115
- procline = "resque: Forked #{@child} at #{Time.now.to_i}"
116
- $0 = procline
117
- log! procline
117
+ procline "Forked #{@child} at #{Time.now.to_i}"
118
118
  Process.wait
119
119
  else
120
- procline = "resque: Processing #{job.queue} since #{Time.now.to_i}"
121
- $0 = procline
122
- log! procline
123
- process(job, &block)
120
+ procline "Processing #{job.queue} since #{Time.now.to_i}"
121
+ perform(job, &block)
124
122
  exit! unless @cant_fork
125
123
  end
126
124
 
125
+ done_working
127
126
  @child = nil
128
127
  else
129
128
  break if interval.to_i == 0
130
129
  log! "Sleeping for #{interval.to_i}"
131
- $0 = @paused ? "resque: Paused" : "resque: Waiting for #{@queues.join(',')}"
130
+ procline @paused ? "Paused" : "Waiting for #{@queues.join(',')}"
132
131
  sleep interval.to_i
133
132
  end
134
133
  end
@@ -137,13 +136,21 @@ module Resque
137
136
  unregister_worker
138
137
  end
139
138
 
140
- # Processes a single job. If none is given, it will try to produce
141
- # one.
142
- def process(job = nil)
139
+ # DEPRECATED. Processes a single job. If none is given, it will
140
+ # try to produce one. Usually run in the child.
141
+ def process(job = nil, &block)
143
142
  return unless job ||= reserve
144
143
 
144
+ working_on job
145
+ perform(job, &block)
146
+ ensure
147
+ done_working
148
+ end
149
+
150
+ # Processes a given job in the child.
151
+ def perform(job)
145
152
  begin
146
- working_on job
153
+ run_hook :after_fork, job
147
154
  job.perform
148
155
  rescue Object => e
149
156
  log "#{job.inspect} failed: #{e.inspect}"
@@ -153,7 +160,6 @@ module Resque
153
160
  log "done: #{job.inspect}"
154
161
  ensure
155
162
  yield job if block_given?
156
- done_working
157
163
  end
158
164
  end
159
165
 
@@ -203,7 +209,12 @@ module Resque
203
209
  enable_gc_optimizations
204
210
  register_signal_handlers
205
211
  prune_dead_workers
212
+ run_hook :before_first_fork
206
213
  register_worker
214
+
215
+ # Fix buffering so we can `rake resque:work > resque.log` and
216
+ # get output from the child in there.
217
+ $stdout.sync = true
207
218
  end
208
219
 
209
220
  # Enables GC Optimizations if you're running REE.
@@ -307,8 +318,28 @@ module Resque
307
318
  started!
308
319
  end
309
320
 
321
+ # Runs a named hook, passing along any arguments.
322
+ def run_hook(name, *args)
323
+ return unless hook = Resque.send(name)
324
+ msg = "Running #{name} hook"
325
+ msg << " with #{args.inspect}" if args.any?
326
+ log msg
327
+
328
+ args.any? ? hook.call(*args) : hook.call
329
+ end
330
+
310
331
  # Unregisters ourself as a worker. Useful when shutting down.
311
332
  def unregister_worker
333
+ # If we're still processing a job, make sure it gets logged as a
334
+ # failure.
335
+ if (hash = processing) && !hash.empty?
336
+ job = Job.new(hash['queue'], hash['payload'])
337
+ # Ensure the proper worker is attached to this job, even if
338
+ # it's not the precise instance that died.
339
+ job.worker = self
340
+ job.fail(DirtyExit.new)
341
+ end
342
+
312
343
  mongo_workers.remove(:worker => self.to_s)
313
344
 
314
345
  Stat.clear("processed:#{self}")
@@ -318,13 +349,13 @@ module Resque
318
349
  # Given a job, tells Redis we're working on it. Useful for seeing
319
350
  # what workers are doing and when.
320
351
  def working_on(job)
321
- job.worker = self.to_s
352
+ job.worker = self
322
353
  data = encode \
323
354
  :queue => job.queue,
324
355
  :run_at => Time.now.to_s,
325
356
  :payload => job.payload
326
357
  working_on = {'working_on' => data}
327
- mongo_workers.update({:worker => self.to_s}, {'$set' => working_on})
358
+ mongo_workers.update({:worker => self.to_s}, {'$set' => working_on}, :upsert => true )
328
359
  end
329
360
 
330
361
  # Called when we are done working - clears our `working_on` state
@@ -424,6 +455,14 @@ module Resque
424
455
  end
425
456
  end
426
457
 
458
+ # Given a string, sets the procline ($0) and logs.
459
+ # Procline is always in the format of:
460
+ # resque-VERSION: STRING
461
+ def procline(string)
462
+ $0 = "resque-#{Resque::Version}: #{string}"
463
+ log! $0
464
+ end
465
+
427
466
  # Log a message to STDOUT if we are verbose or very_verbose.
428
467
  def log(message)
429
468
  if verbose
@@ -1,20 +1,28 @@
1
1
  # Inspired by rabbitmq.rake the Redbox project at http://github.com/rick/redbox/tree/master
2
2
  require 'fileutils'
3
3
  require 'open-uri'
4
+ require 'pathname'
4
5
 
5
6
  class RedisRunner
6
7
 
7
- def self.redisdir
8
- "/tmp/redis/"
8
+ def self.redis_dir
9
+ @redis_dir ||= if ENV['PREFIX']
10
+ Pathname.new(ENV['PREFIX'])
11
+ else
12
+ Pathname.new(`which redis-server`) + '..' + '..'
13
+ end
9
14
  end
10
15
 
11
- def self.redisconfdir
12
- server_dir = File.dirname(`which redis-server`)
13
- conf_file = "#{server_dir}/../etc/redis.conf"
14
- unless File.exists? conf_file
15
- conf_file = "#{server_dir}/../../etc/redis.conf"
16
- end
17
- conf_file
16
+ def self.bin_dir
17
+ redis_dir + 'bin'
18
+ end
19
+
20
+ def self.config
21
+ @config ||= if File.exists?(redis_dir + 'etc/redis.conf')
22
+ redis_dir + 'etc/redis.conf'
23
+ else
24
+ redis_dir + '../etc/redis.conf'
25
+ end
18
26
  end
19
27
 
20
28
  def self.dtach_socket
@@ -29,12 +37,12 @@ class RedisRunner
29
37
  def self.start
30
38
  puts 'Detach with Ctrl+\ Re-attach with rake redis:attach'
31
39
  sleep 1
32
- command = "dtach -A #{dtach_socket} redis-server #{redisconfdir}"
40
+ command = "#{bin_dir}/dtach -A #{dtach_socket} #{bin_dir}/redis-server #{config}"
33
41
  sh command
34
42
  end
35
43
 
36
44
  def self.attach
37
- exec "dtach -a #{dtach_socket}"
45
+ exec "#{bin_dir}/dtach -a #{dtach_socket}"
38
46
  end
39
47
 
40
48
  def self.stop
@@ -73,31 +81,39 @@ namespace :redis do
73
81
 
74
82
  desc 'Install the latest verison of Redis from Github (requires git, duh)'
75
83
  task :install => [:about, :download, :make] do
76
- ENV['PREFIX'] and bin_dir = "#{ENV['PREFIX']}/bin" or bin_dir = '/usr/bin'
84
+ bin_dir = '/usr/bin'
85
+ conf_dir = '/etc'
86
+
87
+ if ENV['PREFIX']
88
+ bin_dir = "#{ENV['PREFIX']}/bin"
89
+ sh "mkdir -p #{bin_dir}" unless File.exists?("#{bin_dir}")
90
+
91
+ conf_dir = "#{ENV['PREFIX']}/etc"
92
+ sh "mkdir -p #{conf_dir}" unless File.exists?("#{conf_dir}")
93
+ end
94
+
77
95
  %w(redis-benchmark redis-cli redis-server).each do |bin|
78
96
  sh "cp /tmp/redis/#{bin} #{bin_dir}"
79
97
  end
80
98
 
81
99
  puts "Installed redis-benchmark, redis-cli and redis-server to #{bin_dir}"
82
100
 
83
- ENV['PREFIX'] and conf_dir = "#{ENV['PREFIX']}/etc" or conf_dir = '/etc'
84
101
  unless File.exists?("#{conf_dir}/redis.conf")
85
- sh "mkdir #{conf_dir}" unless File.exists?("#{conf_dir}")
86
102
  sh "cp /tmp/redis/redis.conf #{conf_dir}/redis.conf"
87
103
  puts "Installed redis.conf to #{conf_dir} \n You should look at this file!"
88
104
  end
89
105
  end
90
106
 
91
107
  task :make do
92
- sh "cd #{RedisRunner.redisdir} && make clean"
93
- sh "cd #{RedisRunner.redisdir} && make"
108
+ sh "cd /tmp/redis && make clean"
109
+ sh "cd /tmp/redis && make"
94
110
  end
95
111
 
96
112
  desc "Download package"
97
113
  task :download do
98
- sh 'rm -rf /tmp/redis/' if File.exists?("#{RedisRunner.redisdir}/.svn")
99
- sh 'git clone git://github.com/antirez/redis.git /tmp/redis' unless File.exists?(RedisRunner.redisdir)
100
- sh "cd #{RedisRunner.redisdir} && git pull" if File.exists?("#{RedisRunner.redisdir}/.git")
114
+ sh 'rm -rf /tmp/redis/' if File.exists?("/tmp/redis/.svn")
115
+ sh 'git clone git://github.com/antirez/redis.git /tmp/redis' unless File.exists?('/tmp/redis')
116
+ sh "cd /tmp/redis && git pull" if File.exists?("/tmp/redis/.git")
101
117
  end
102
118
 
103
119
  end
@@ -110,9 +126,24 @@ namespace :dtach do
110
126
  end
111
127
 
112
128
  desc 'Install dtach 0.8 from source'
113
- task :install => [:about] do
129
+ task :install => [:about, :download, :make] do
130
+
131
+ bin_dir = "/usr/bin"
132
+
133
+ if ENV['PREFIX']
134
+ bin_dir = "#{ENV['PREFIX']}/bin"
135
+ sh "mkdir -p #{bin_dir}" unless File.exists?("#{bin_dir}")
136
+ end
137
+
138
+ sh "cp /tmp/dtach-0.8/dtach #{bin_dir}"
139
+ end
140
+
141
+ task :make do
142
+ sh 'cd /tmp/dtach-0.8/ && ./configure && make'
143
+ end
114
144
 
115
- Dir.chdir('/tmp/')
145
+ desc "Download package"
146
+ task :download do
116
147
  unless File.exists?('/tmp/dtach-0.8.tar.gz')
117
148
  require 'net/http'
118
149
 
@@ -121,15 +152,8 @@ namespace :dtach do
121
152
  end
122
153
 
123
154
  unless File.directory?('/tmp/dtach-0.8')
124
- system('tar xzf dtach-0.8.tar.gz')
155
+ sh 'cd /tmp && tar xzf dtach-0.8.tar.gz'
125
156
  end
126
-
127
- ENV['PREFIX'] and bin_dir = "#{ENV['PREFIX']}/bin" or bin_dir = "/usr/bin"
128
- Dir.chdir('/tmp/dtach-0.8/')
129
- sh 'cd /tmp/dtach-0.8/ && ./configure && make'
130
- sh "cp /tmp/dtach-0.8/dtach #{bin_dir}"
131
-
132
- puts "Dtach successfully installed to #{bin_dir}"
133
157
  end
134
158
  end
135
159
 
@@ -0,0 +1,302 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ context "Resque::Job before_perform" do
4
+ include PerformJob
5
+
6
+ class BeforePerformJob
7
+ def self.before_perform_record_history(history)
8
+ history << :before_perform
9
+ end
10
+
11
+ def self.perform(history)
12
+ history << :perform
13
+ end
14
+ end
15
+
16
+ test "it runs before_perform before perform" do
17
+ result = perform_job(BeforePerformJob, history=[])
18
+ assert_equal true, result, "perform returned true"
19
+ assert_equal history, [:before_perform, :perform]
20
+ end
21
+
22
+ class BeforePerformJobFails
23
+ def self.before_perform_fail_job(history)
24
+ history << :before_perform
25
+ raise StandardError
26
+ end
27
+ def self.perform(history)
28
+ history << :perform
29
+ end
30
+ end
31
+
32
+ test "raises an error and does not perform if before_perform fails" do
33
+ history = []
34
+ assert_raises StandardError do
35
+ perform_job(BeforePerformJobFails, history)
36
+ end
37
+ assert_equal history, [:before_perform], "Only before_perform was run"
38
+ end
39
+
40
+ class BeforePerformJobAborts
41
+ def self.before_perform_abort(history)
42
+ history << :before_perform
43
+ raise Resque::Job::DontPerform
44
+ end
45
+ def self.perform(history)
46
+ history << :perform
47
+ end
48
+ end
49
+
50
+ test "does not perform if before_perform raises Resque::Job::DontPerform" do
51
+ result = perform_job(BeforePerformJobAborts, history=[])
52
+ assert_equal false, result, "perform returned false"
53
+ assert_equal history, [:before_perform], "Only before_perform was run"
54
+ end
55
+ end
56
+
57
+ context "Resque::Job after_perform" do
58
+ include PerformJob
59
+
60
+ class AfterPerformJob
61
+ def self.perform(history)
62
+ history << :perform
63
+ end
64
+ def self.after_perform_record_history(history)
65
+ history << :after_perform
66
+ end
67
+ end
68
+
69
+ test "it runs after_perform after perform" do
70
+ result = perform_job(AfterPerformJob, history=[])
71
+ assert_equal true, result, "perform returned true"
72
+ assert_equal history, [:perform, :after_perform]
73
+ end
74
+
75
+ class AfterPerformJobFails
76
+ def self.perform(history)
77
+ history << :perform
78
+ end
79
+ def self.after_perform_fail_job(history)
80
+ history << :after_perform
81
+ raise StandardError
82
+ end
83
+ end
84
+
85
+ test "raises an error but has already performed if after_perform fails" do
86
+ history = []
87
+ assert_raises StandardError do
88
+ perform_job(AfterPerformJobFails, history)
89
+ end
90
+ assert_equal history, [:perform, :after_perform], "Only after_perform was run"
91
+ end
92
+ end
93
+
94
+ context "Resque::Job around_perform" do
95
+ include PerformJob
96
+
97
+ class AroundPerformJob
98
+ def self.perform(history)
99
+ history << :perform
100
+ end
101
+ def self.around_perform_record_history(history)
102
+ history << :start_around_perform
103
+ yield
104
+ history << :finish_around_perform
105
+ end
106
+ end
107
+
108
+ test "it runs around_perform then yields in order to perform" do
109
+ result = perform_job(AroundPerformJob, history=[])
110
+ assert_equal true, result, "perform returned true"
111
+ assert_equal history, [:start_around_perform, :perform, :finish_around_perform]
112
+ end
113
+
114
+ class AroundPerformJobFailsBeforePerforming
115
+ def self.perform(history)
116
+ history << :perform
117
+ end
118
+ def self.around_perform_fail(history)
119
+ history << :start_around_perform
120
+ raise StandardError
121
+ yield
122
+ history << :finish_around_perform
123
+ end
124
+ end
125
+
126
+ test "raises an error and does not perform if around_perform fails before yielding" do
127
+ history = []
128
+ assert_raises StandardError do
129
+ perform_job(AroundPerformJobFailsBeforePerforming, history)
130
+ end
131
+ assert_equal history, [:start_around_perform], "Only part of around_perform was run"
132
+ end
133
+
134
+ class AroundPerformJobFailsWhilePerforming
135
+ def self.perform(history)
136
+ history << :perform
137
+ raise StandardError
138
+ end
139
+ def self.around_perform_fail_in_yield(history)
140
+ history << :start_around_perform
141
+ begin
142
+ yield
143
+ ensure
144
+ history << :ensure_around_perform
145
+ end
146
+ history << :finish_around_perform
147
+ end
148
+ end
149
+
150
+ test "raises an error but may handle exceptions if perform fails" do
151
+ history = []
152
+ assert_raises StandardError do
153
+ perform_job(AroundPerformJobFailsWhilePerforming, history)
154
+ end
155
+ assert_equal history, [:start_around_perform, :perform, :ensure_around_perform], "Only part of around_perform was run"
156
+ end
157
+
158
+ class AroundPerformJobDoesNotHaveToYield
159
+ def self.perform(history)
160
+ history << :perform
161
+ end
162
+ def self.around_perform_dont_yield(history)
163
+ history << :start_around_perform
164
+ history << :finish_around_perform
165
+ end
166
+ end
167
+
168
+ test "around_perform is not required to yield" do
169
+ history = []
170
+ result = perform_job(AroundPerformJobDoesNotHaveToYield, history)
171
+ assert_equal false, result, "perform returns false"
172
+ assert_equal history, [:start_around_perform, :finish_around_perform], "perform was not run"
173
+ end
174
+ end
175
+
176
+ context "Resque::Job on_failure" do
177
+ include PerformJob
178
+
179
+ class FailureJobThatDoesNotFail
180
+ def self.perform(history)
181
+ history << :perform
182
+ end
183
+ def self.on_failure_record_failure(exception, history)
184
+ history << exception.message
185
+ end
186
+ end
187
+
188
+ test "it does not call on_failure if no failures occur" do
189
+ result = perform_job(FailureJobThatDoesNotFail, history=[])
190
+ assert_equal true, result, "perform returned true"
191
+ assert_equal history, [:perform]
192
+ end
193
+
194
+ class FailureJobThatFails
195
+ def self.perform(history)
196
+ history << :perform
197
+ raise StandardError, "oh no"
198
+ end
199
+ def self.on_failure_record_failure(exception, history)
200
+ history << exception.message
201
+ end
202
+ end
203
+
204
+ test "it calls on_failure with the exception and then re-raises the exception" do
205
+ history = []
206
+ assert_raises StandardError do
207
+ perform_job(FailureJobThatFails, history)
208
+ end
209
+ assert_equal history, [:perform, "oh no"]
210
+ end
211
+
212
+ class FailureJobThatFailsBadly
213
+ def self.perform(history)
214
+ history << :perform
215
+ raise SyntaxError, "oh no"
216
+ end
217
+ def self.on_failure_record_failure(exception, history)
218
+ history << exception.message
219
+ end
220
+ end
221
+
222
+ test "it calls on_failure even with bad exceptions" do
223
+ history = []
224
+ assert_raises SyntaxError do
225
+ perform_job(FailureJobThatFailsBadly, history)
226
+ end
227
+ assert_equal history, [:perform, "oh no"]
228
+ end
229
+ end
230
+
231
+ context "Resque::Job all hooks" do
232
+ include PerformJob
233
+
234
+ class VeryHookyJob
235
+ def self.before_perform_record_history(history)
236
+ history << :before_perform
237
+ end
238
+ def self.around_perform_record_history(history)
239
+ history << :start_around_perform
240
+ yield
241
+ history << :finish_around_perform
242
+ end
243
+ def self.perform(history)
244
+ history << :perform
245
+ end
246
+ def self.after_perform_record_history(history)
247
+ history << :after_perform
248
+ end
249
+ def self.on_failure_record_history(exception, history)
250
+ history << exception.message
251
+ end
252
+ end
253
+
254
+ test "the complete hook order" do
255
+ result = perform_job(VeryHookyJob, history=[])
256
+ assert_equal true, result, "perform returned true"
257
+ assert_equal history, [
258
+ :before_perform,
259
+ :start_around_perform,
260
+ :perform,
261
+ :finish_around_perform,
262
+ :after_perform
263
+ ]
264
+ end
265
+
266
+ class VeryHookyJobThatFails
267
+ def self.before_perform_record_history(history)
268
+ history << :before_perform
269
+ end
270
+ def self.around_perform_record_history(history)
271
+ history << :start_around_perform
272
+ yield
273
+ history << :finish_around_perform
274
+ end
275
+ def self.perform(history)
276
+ history << :perform
277
+ end
278
+ def self.after_perform_record_history(history)
279
+ history << :after_perform
280
+ raise StandardError, "oh no"
281
+ end
282
+ def self.on_failure_record_history(exception, history)
283
+ history << exception.message
284
+ end
285
+ end
286
+
287
+ test "the complete hook order with a failure at the last minute" do
288
+ history = []
289
+ assert_raises StandardError do
290
+ perform_job(VeryHookyJobThatFails, history)
291
+ end
292
+ assert_equal history, [
293
+ :before_perform,
294
+ :start_around_perform,
295
+ :perform,
296
+ :finish_around_perform,
297
+ :after_perform,
298
+ "oh no"
299
+ ]
300
+ end
301
+ end
302
+