resque-mongo 1.4.0 → 1.8.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 (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
+