resque-cedar 1.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/HISTORY.md +354 -0
  2. data/LICENSE +20 -0
  3. data/README.markdown +908 -0
  4. data/Rakefile +70 -0
  5. data/bin/resque +81 -0
  6. data/bin/resque-web +27 -0
  7. data/lib/resque.rb +385 -0
  8. data/lib/resque/coder.rb +27 -0
  9. data/lib/resque/errors.rb +10 -0
  10. data/lib/resque/failure.rb +96 -0
  11. data/lib/resque/failure/airbrake.rb +17 -0
  12. data/lib/resque/failure/base.rb +64 -0
  13. data/lib/resque/failure/hoptoad.rb +33 -0
  14. data/lib/resque/failure/multiple.rb +54 -0
  15. data/lib/resque/failure/redis.rb +51 -0
  16. data/lib/resque/failure/thoughtbot.rb +33 -0
  17. data/lib/resque/helpers.rb +64 -0
  18. data/lib/resque/job.rb +223 -0
  19. data/lib/resque/multi_json_coder.rb +37 -0
  20. data/lib/resque/multi_queue.rb +73 -0
  21. data/lib/resque/plugin.rb +66 -0
  22. data/lib/resque/queue.rb +117 -0
  23. data/lib/resque/server.rb +248 -0
  24. data/lib/resque/server/public/favicon.ico +0 -0
  25. data/lib/resque/server/public/idle.png +0 -0
  26. data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
  27. data/lib/resque/server/public/jquery.relatize_date.js +95 -0
  28. data/lib/resque/server/public/poll.png +0 -0
  29. data/lib/resque/server/public/ranger.js +73 -0
  30. data/lib/resque/server/public/reset.css +44 -0
  31. data/lib/resque/server/public/style.css +86 -0
  32. data/lib/resque/server/public/working.png +0 -0
  33. data/lib/resque/server/test_helper.rb +19 -0
  34. data/lib/resque/server/views/error.erb +1 -0
  35. data/lib/resque/server/views/failed.erb +67 -0
  36. data/lib/resque/server/views/key_sets.erb +19 -0
  37. data/lib/resque/server/views/key_string.erb +11 -0
  38. data/lib/resque/server/views/layout.erb +44 -0
  39. data/lib/resque/server/views/next_more.erb +10 -0
  40. data/lib/resque/server/views/overview.erb +4 -0
  41. data/lib/resque/server/views/queues.erb +49 -0
  42. data/lib/resque/server/views/stats.erb +62 -0
  43. data/lib/resque/server/views/workers.erb +109 -0
  44. data/lib/resque/server/views/working.erb +72 -0
  45. data/lib/resque/stat.rb +53 -0
  46. data/lib/resque/tasks.rb +61 -0
  47. data/lib/resque/version.rb +3 -0
  48. data/lib/resque/worker.rb +557 -0
  49. data/lib/tasks/redis.rake +161 -0
  50. data/lib/tasks/resque.rake +2 -0
  51. data/test/airbrake_test.rb +26 -0
  52. data/test/hoptoad_test.rb +26 -0
  53. data/test/job_hooks_test.rb +423 -0
  54. data/test/job_plugins_test.rb +230 -0
  55. data/test/multi_queue_test.rb +95 -0
  56. data/test/plugin_test.rb +116 -0
  57. data/test/redis-test-cluster.conf +115 -0
  58. data/test/redis-test.conf +115 -0
  59. data/test/redis_queue_test.rb +133 -0
  60. data/test/resque-web_test.rb +59 -0
  61. data/test/resque_test.rb +284 -0
  62. data/test/test_helper.rb +135 -0
  63. data/test/worker_test.rb +443 -0
  64. metadata +188 -0
@@ -0,0 +1,135 @@
1
+ require 'rubygems'
2
+
3
+ dir = File.dirname(File.expand_path(__FILE__))
4
+ $LOAD_PATH.unshift dir + '/../lib'
5
+ $TESTING = true
6
+ require 'mocha'
7
+ require 'minitest/unit'
8
+ require 'minitest/spec'
9
+ require 'test/unit'
10
+
11
+ require 'redis/namespace'
12
+ require 'resque'
13
+
14
+ #
15
+ # make sure we can run redis
16
+ #
17
+
18
+ if !system("which redis-server")
19
+ puts '', "** can't find `redis-server` in your path"
20
+ puts "** try running `sudo rake install`"
21
+ abort ''
22
+ end
23
+
24
+
25
+ #
26
+ # start our own redis when the tests start,
27
+ # kill it when they end
28
+ #
29
+
30
+ at_exit do
31
+ next if $!
32
+
33
+ if defined?(MiniTest)
34
+ exit_code = MiniTest::Unit.new.run(ARGV)
35
+ else
36
+ exit_code = Test::Unit::AutoRunner.run
37
+ end
38
+
39
+ processes = `ps -A -o pid,command | grep [r]edis-test`.split("\n")
40
+ pids = processes.map { |process| process.split(" ")[0] }
41
+ puts "Killing test redis server..."
42
+ `rm -f #{dir}/dump.rdb #{dir}/dump-cluster.rdb`
43
+ pids.each { |pid| Process.kill("KILL", pid.to_i) }
44
+ exit exit_code
45
+ end
46
+
47
+ if ENV.key? 'RESQUE_DISTRIBUTED'
48
+ require 'redis/distributed'
49
+ puts "Starting redis for testing at localhost:9736 and localhost:9737..."
50
+ `redis-server #{dir}/redis-test.conf`
51
+ `redis-server #{dir}/redis-test-cluster.conf`
52
+ r = Redis::Distributed.new(['redis://localhost:9736', 'redis://localhost:9737'])
53
+ Resque.redis = Redis::Namespace.new :resque, :redis => r
54
+ else
55
+ puts "Starting redis for testing at localhost:9736..."
56
+ `redis-server #{dir}/redis-test.conf`
57
+ Resque.redis = 'localhost:9736'
58
+ end
59
+
60
+
61
+ ##
62
+ # Helper to perform job classes
63
+ #
64
+ module PerformJob
65
+ def perform_job(klass, *args)
66
+ resque_job = Resque::Job.new(:testqueue, 'class' => klass, 'args' => args)
67
+ resque_job.perform
68
+ end
69
+ end
70
+
71
+ #
72
+ # fixture classes
73
+ #
74
+
75
+ class SomeJob
76
+ def self.perform(repo_id, path)
77
+ end
78
+ end
79
+
80
+ class SomeIvarJob < SomeJob
81
+ @queue = :ivar
82
+ end
83
+
84
+ class SomeMethodJob < SomeJob
85
+ def self.queue
86
+ :method
87
+ end
88
+ end
89
+
90
+ class BadJob
91
+ def self.perform
92
+ raise "Bad job!"
93
+ end
94
+ end
95
+
96
+ class GoodJob
97
+ def self.perform(name)
98
+ "Good job, #{name}"
99
+ end
100
+ end
101
+
102
+ class BadJobWithSyntaxError
103
+ def self.perform
104
+ raise SyntaxError, "Extra Bad job!"
105
+ end
106
+ end
107
+
108
+ class BadFailureBackend < Resque::Failure::Base
109
+ def save
110
+ raise Exception.new("Failure backend error")
111
+ end
112
+ end
113
+
114
+ def with_failure_backend(failure_backend, &block)
115
+ previous_backend = Resque::Failure.backend
116
+ Resque::Failure.backend = failure_backend
117
+ yield block
118
+ ensure
119
+ Resque::Failure.backend = previous_backend
120
+ end
121
+
122
+ class Time
123
+ # Thanks, Timecop
124
+ class << self
125
+ attr_accessor :fake_time
126
+
127
+ alias_method :now_without_mock_time, :now
128
+
129
+ def now
130
+ fake_time || now_without_mock_time
131
+ end
132
+ end
133
+
134
+ self.fake_time = nil
135
+ end
@@ -0,0 +1,443 @@
1
+ require 'test_helper'
2
+
3
+ describe "Resque::Worker" do
4
+ include Test::Unit::Assertions
5
+
6
+ before do
7
+ Resque.redis = Resque.redis # reset state in Resque object
8
+ Resque.redis.flushall
9
+
10
+ Resque.before_first_fork = nil
11
+ Resque.before_fork = nil
12
+ Resque.after_fork = nil
13
+
14
+ @worker = Resque::Worker.new(:jobs)
15
+ Resque::Job.create(:jobs, SomeJob, 20, '/tmp')
16
+ end
17
+
18
+ it "can fail jobs" do
19
+ Resque::Job.create(:jobs, BadJob)
20
+ @worker.work(0)
21
+ assert_equal 1, Resque::Failure.count
22
+ end
23
+
24
+ it "failed jobs report exception and message" do
25
+ Resque::Job.create(:jobs, BadJobWithSyntaxError)
26
+ @worker.work(0)
27
+ assert_equal('SyntaxError', Resque::Failure.all['exception'])
28
+ assert_equal('Extra Bad job!', Resque::Failure.all['error'])
29
+ end
30
+
31
+ it "does not allow exceptions from failure backend to escape" do
32
+ job = Resque::Job.new(:jobs, {})
33
+ with_failure_backend BadFailureBackend do
34
+ @worker.perform job
35
+ end
36
+ end
37
+
38
+ it "fails uncompleted jobs on exit" do
39
+ job = Resque::Job.new(:jobs, {'class' => 'GoodJob', 'args' => "blah"})
40
+ @worker.working_on(job)
41
+ @worker.unregister_worker
42
+ assert_equal 1, Resque::Failure.count
43
+ end
44
+
45
+ class ::SimpleJobWithFailureHandling
46
+ def self.on_failure_record_failure(exception, *job_args)
47
+ @@exception = exception
48
+ end
49
+
50
+ def self.exception
51
+ @@exception
52
+ end
53
+ end
54
+
55
+ it "fails uncompleted jobs on exit, and calls failure hook" do
56
+ job = Resque::Job.new(:jobs, {'class' => 'SimpleJobWithFailureHandling', 'args' => ""})
57
+ @worker.working_on(job)
58
+ @worker.unregister_worker
59
+ assert_equal 1, Resque::Failure.count
60
+ assert(SimpleJobWithFailureHandling.exception.kind_of?(Resque::DirtyExit))
61
+ end
62
+
63
+ it "can peek at failed jobs" do
64
+ 10.times { Resque::Job.create(:jobs, BadJob) }
65
+ @worker.work(0)
66
+ assert_equal 10, Resque::Failure.count
67
+
68
+ assert_equal 10, Resque::Failure.all(0, 20).size
69
+ end
70
+
71
+ it "can clear failed jobs" do
72
+ Resque::Job.create(:jobs, BadJob)
73
+ @worker.work(0)
74
+ assert_equal 1, Resque::Failure.count
75
+ Resque::Failure.clear
76
+ assert_equal 0, Resque::Failure.count
77
+ end
78
+
79
+ it "catches exceptional jobs" do
80
+ Resque::Job.create(:jobs, BadJob)
81
+ Resque::Job.create(:jobs, BadJob)
82
+ @worker.process
83
+ @worker.process
84
+ @worker.process
85
+ assert_equal 2, Resque::Failure.count
86
+ end
87
+
88
+ it "strips whitespace from queue names" do
89
+ queues = "critical, high, low".split(',')
90
+ worker = Resque::Worker.new(*queues)
91
+ assert_equal %w( critical high low ), worker.queues
92
+ end
93
+
94
+ it "can work on multiple queues" do
95
+ Resque::Job.create(:high, GoodJob)
96
+ Resque::Job.create(:critical, GoodJob)
97
+
98
+ worker = Resque::Worker.new(:critical, :high)
99
+
100
+ worker.process
101
+ assert_equal 1, Resque.size(:high)
102
+ assert_equal 0, Resque.size(:critical)
103
+
104
+ worker.process
105
+ assert_equal 0, Resque.size(:high)
106
+ end
107
+
108
+ it "can work on all queues" do
109
+ Resque::Job.create(:high, GoodJob)
110
+ Resque::Job.create(:critical, GoodJob)
111
+ Resque::Job.create(:blahblah, GoodJob)
112
+
113
+ worker = Resque::Worker.new("*")
114
+
115
+ worker.work(0)
116
+ assert_equal 0, Resque.size(:high)
117
+ assert_equal 0, Resque.size(:critical)
118
+ assert_equal 0, Resque.size(:blahblah)
119
+ end
120
+
121
+ it "can work with wildcard at the end of the list" do
122
+ Resque::Job.create(:high, GoodJob)
123
+ Resque::Job.create(:critical, GoodJob)
124
+ Resque::Job.create(:blahblah, GoodJob)
125
+ Resque::Job.create(:beer, GoodJob)
126
+
127
+ worker = Resque::Worker.new(:critical, :high, "*")
128
+
129
+ worker.work(0)
130
+ assert_equal 0, Resque.size(:high)
131
+ assert_equal 0, Resque.size(:critical)
132
+ assert_equal 0, Resque.size(:blahblah)
133
+ assert_equal 0, Resque.size(:beer)
134
+ end
135
+
136
+ it "can work with wildcard at the middle of the list" do
137
+ Resque::Job.create(:high, GoodJob)
138
+ Resque::Job.create(:critical, GoodJob)
139
+ Resque::Job.create(:blahblah, GoodJob)
140
+ Resque::Job.create(:beer, GoodJob)
141
+
142
+ worker = Resque::Worker.new(:critical, "*", :high)
143
+
144
+ worker.work(0)
145
+ assert_equal 0, Resque.size(:high)
146
+ assert_equal 0, Resque.size(:critical)
147
+ assert_equal 0, Resque.size(:blahblah)
148
+ assert_equal 0, Resque.size(:beer)
149
+ end
150
+
151
+ it "processes * queues in alphabetical order" do
152
+ Resque::Job.create(:high, GoodJob)
153
+ Resque::Job.create(:critical, GoodJob)
154
+ Resque::Job.create(:blahblah, GoodJob)
155
+
156
+ worker = Resque::Worker.new("*")
157
+ processed_queues = []
158
+
159
+ worker.work(0) do |job|
160
+ processed_queues << job.queue
161
+ end
162
+
163
+ assert_equal %w( jobs high critical blahblah ).sort, processed_queues
164
+ end
165
+
166
+ it "can work with dynamically added queues when using wildcard" do
167
+ worker = Resque::Worker.new("*")
168
+
169
+ assert_equal ["jobs"], Resque.queues
170
+
171
+ Resque::Job.create(:high, GoodJob)
172
+ Resque::Job.create(:critical, GoodJob)
173
+ Resque::Job.create(:blahblah, GoodJob)
174
+
175
+ processed_queues = []
176
+
177
+ worker.work(0) do |job|
178
+ processed_queues << job.queue
179
+ end
180
+
181
+ assert_equal %w( jobs high critical blahblah ).sort, processed_queues
182
+ end
183
+
184
+ it "has a unique id" do
185
+ assert_equal "#{`hostname`.chomp}:#{$$}:jobs", @worker.to_s
186
+ end
187
+
188
+ it "complains if no queues are given" do
189
+ assert_raise Resque::NoQueueError do
190
+ Resque::Worker.new
191
+ end
192
+ end
193
+
194
+ it "fails if a job class has no `perform` method" do
195
+ worker = Resque::Worker.new(:perform_less)
196
+ Resque::Job.create(:perform_less, Object)
197
+
198
+ assert_equal 0, Resque::Failure.count
199
+ worker.work(0)
200
+ assert_equal 1, Resque::Failure.count
201
+ end
202
+
203
+ it "inserts itself into the 'workers' list on startup" do
204
+ @worker.work(0) do
205
+ assert_equal @worker, Resque.workers[0]
206
+ end
207
+ end
208
+
209
+ it "removes itself from the 'workers' list on shutdown" do
210
+ @worker.work(0) do
211
+ assert_equal @worker, Resque.workers[0]
212
+ end
213
+
214
+ assert_equal [], Resque.workers
215
+ end
216
+
217
+ it "removes worker with stringified id" do
218
+ @worker.work(0) do
219
+ worker_id = Resque.workers[0].to_s
220
+ Resque.remove_worker(worker_id)
221
+ assert_equal [], Resque.workers
222
+ end
223
+ end
224
+
225
+ it "records what it is working on" do
226
+ @worker.work(0) do
227
+ task = @worker.job
228
+ assert_equal({"args"=>[20, "/tmp"], "class"=>"SomeJob"}, task['payload'])
229
+ assert task['run_at']
230
+ assert_equal 'jobs', task['queue']
231
+ end
232
+ end
233
+
234
+ it "clears its status when not working on anything" do
235
+ @worker.work(0)
236
+ assert_equal Hash.new, @worker.job
237
+ end
238
+
239
+ it "knows when it is working" do
240
+ @worker.work(0) do
241
+ assert @worker.working?
242
+ end
243
+ end
244
+
245
+ it "knows when it is idle" do
246
+ @worker.work(0)
247
+ assert @worker.idle?
248
+ end
249
+
250
+ it "knows who is working" do
251
+ @worker.work(0) do
252
+ assert_equal [@worker], Resque.working
253
+ end
254
+ end
255
+
256
+ it "keeps track of how many jobs it has processed" do
257
+ Resque::Job.create(:jobs, BadJob)
258
+ Resque::Job.create(:jobs, BadJob)
259
+
260
+ 3.times do
261
+ job = @worker.reserve
262
+ @worker.process job
263
+ end
264
+ assert_equal 3, @worker.processed
265
+ end
266
+
267
+ it "reserve blocks when the queue is empty" do
268
+ worker = Resque::Worker.new(:timeout)
269
+
270
+ assert_raises Timeout::Error do
271
+ Timeout.timeout(1) { worker.reserve(5) }
272
+ end
273
+ end
274
+
275
+ it "reserve returns nil when there is no job and is polling" do
276
+ worker = Resque::Worker.new(:timeout)
277
+
278
+ assert_equal nil, worker.reserve(1)
279
+ end
280
+
281
+ it "keeps track of how many failures it has seen" do
282
+ Resque::Job.create(:jobs, BadJob)
283
+ Resque::Job.create(:jobs, BadJob)
284
+
285
+ 3.times do
286
+ job = @worker.reserve
287
+ @worker.process job
288
+ end
289
+ assert_equal 2, @worker.failed
290
+ end
291
+
292
+ it "stats are erased when the worker goes away" do
293
+ @worker.work(0)
294
+ assert_equal 0, @worker.processed
295
+ assert_equal 0, @worker.failed
296
+ end
297
+
298
+ it "knows when it started" do
299
+ time = Time.now
300
+ @worker.work(0) do
301
+ assert_equal time.to_s, @worker.started.to_s
302
+ end
303
+ end
304
+
305
+ it "knows whether it exists or not" do
306
+ @worker.work(0) do
307
+ assert Resque::Worker.exists?(@worker)
308
+ assert !Resque::Worker.exists?('blah-blah')
309
+ end
310
+ end
311
+
312
+ it "sets $0 while working" do
313
+ @worker.work(0) do
314
+ ver = Resque::Version
315
+ assert_equal "resque-#{ver}: Processing jobs since #{Time.now.to_i}", $0
316
+ end
317
+ end
318
+
319
+ it "can be found" do
320
+ @worker.work(0) do
321
+ found = Resque::Worker.find(@worker.to_s)
322
+ assert_equal @worker.to_s, found.to_s
323
+ assert found.working?
324
+ assert_equal @worker.job, found.job
325
+ end
326
+ end
327
+
328
+ it "doesn't find fakes" do
329
+ @worker.work(0) do
330
+ found = Resque::Worker.find('blah-blah')
331
+ assert_equal nil, found
332
+ end
333
+ end
334
+
335
+ it "cleans up dead worker info on start (crash recovery)" do
336
+ # first we fake out two dead workers
337
+ workerA = Resque::Worker.new(:jobs)
338
+ workerA.instance_variable_set(:@to_s, "#{`hostname`.chomp}:1:jobs")
339
+ workerA.register_worker
340
+
341
+ workerB = Resque::Worker.new(:high, :low)
342
+ workerB.instance_variable_set(:@to_s, "#{`hostname`.chomp}:2:high,low")
343
+ workerB.register_worker
344
+
345
+ assert_equal 2, Resque.workers.size
346
+
347
+ # then we prune them
348
+ @worker.work(0) do
349
+ assert_equal 1, Resque.workers.size
350
+ end
351
+ end
352
+
353
+ it "worker_pids returns pids" do
354
+ known_workers = @worker.worker_pids
355
+ assert !known_workers.empty?
356
+ end
357
+
358
+ it "Processed jobs count" do
359
+ @worker.work(0)
360
+ assert_equal 1, Resque.info[:processed]
361
+ end
362
+
363
+ it "Will call a before_first_fork hook only once" do
364
+ $BEFORE_FORK_CALLED = 0
365
+ Resque.before_first_fork = Proc.new { $BEFORE_FORK_CALLED += 1 }
366
+ workerA = Resque::Worker.new(:jobs)
367
+ Resque::Job.create(:jobs, SomeJob, 20, '/tmp')
368
+
369
+ assert_equal 0, $BEFORE_FORK_CALLED
370
+
371
+ workerA.work(0)
372
+ assert_equal 1, $BEFORE_FORK_CALLED
373
+
374
+ # TODO: Verify it's only run once. Not easy.
375
+ # workerA.work(0)
376
+ # assert_equal 1, $BEFORE_FORK_CALLED
377
+ end
378
+
379
+ it "Will call a before_fork hook before forking" do
380
+ $BEFORE_FORK_CALLED = false
381
+ Resque.before_fork = Proc.new { $BEFORE_FORK_CALLED = true }
382
+ workerA = Resque::Worker.new(:jobs)
383
+
384
+ assert !$BEFORE_FORK_CALLED
385
+ Resque::Job.create(:jobs, SomeJob, 20, '/tmp')
386
+ workerA.work(0)
387
+ assert $BEFORE_FORK_CALLED
388
+ end
389
+
390
+ it "very verbose works in the afternoon" do
391
+ begin
392
+ require 'time'
393
+ last_puts = ""
394
+ Time.fake_time = Time.parse("15:44:33 2011-03-02")
395
+
396
+ @worker.extend(Module.new {
397
+ define_method(:puts) { |thing| last_puts = thing }
398
+ })
399
+
400
+ @worker.very_verbose = true
401
+ @worker.log("some log text")
402
+
403
+ assert_match /\*\* \[15:44:33 2011-03-02\] \d+: some log text/, last_puts
404
+ ensure
405
+ Time.fake_time = nil
406
+ end
407
+ end
408
+
409
+ it "Will call an after_fork hook after forking" do
410
+ $AFTER_FORK_CALLED = false
411
+ Resque.after_fork = Proc.new { $AFTER_FORK_CALLED = true }
412
+ workerA = Resque::Worker.new(:jobs)
413
+
414
+ assert !$AFTER_FORK_CALLED
415
+ Resque::Job.create(:jobs, SomeJob, 20, '/tmp')
416
+ workerA.work(0)
417
+ assert $AFTER_FORK_CALLED
418
+ end
419
+
420
+ it "returns PID of running process" do
421
+ assert_equal @worker.to_s.split(":")[1].to_i, @worker.pid
422
+ end
423
+
424
+ it "requeue failed queue" do
425
+ queue = 'good_job'
426
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
427
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue), :queue => 'some_job', :payload => {'class' => 'SomeJob'})
428
+ Resque::Failure.requeue_queue(queue)
429
+ assert Resque::Failure.all(0).has_key?('retried_at')
430
+ assert !Resque::Failure.all(1).has_key?('retried_at')
431
+ end
432
+
433
+ it "remove failed queue" do
434
+ queue = 'good_job'
435
+ queue2 = 'some_job'
436
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
437
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue2), :queue => queue2, :payload => {'class' => 'SomeJob'})
438
+ Resque::Failure.create(:exception => Exception.new, :worker => Resque::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
439
+ Resque::Failure.remove_queue(queue)
440
+ assert_equal queue2, Resque::Failure.all(0)['queue']
441
+ assert_equal 1, Resque::Failure.count
442
+ end
443
+ end