resque_sqs 1.25.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY.md +467 -0
  3. data/LICENSE +20 -0
  4. data/README.markdown +866 -0
  5. data/Rakefile +70 -0
  6. data/bin/resque-sqs +81 -0
  7. data/bin/resque-sqs-web +27 -0
  8. data/lib/resque_sqs/errors.rb +13 -0
  9. data/lib/resque_sqs/failure/airbrake.rb +33 -0
  10. data/lib/resque_sqs/failure/base.rb +73 -0
  11. data/lib/resque_sqs/failure/multiple.rb +59 -0
  12. data/lib/resque_sqs/failure/redis.rb +108 -0
  13. data/lib/resque_sqs/failure/redis_multi_queue.rb +89 -0
  14. data/lib/resque_sqs/failure.rb +113 -0
  15. data/lib/resque_sqs/helpers.rb +107 -0
  16. data/lib/resque_sqs/job.rb +346 -0
  17. data/lib/resque_sqs/log_formatters/quiet_formatter.rb +7 -0
  18. data/lib/resque_sqs/log_formatters/verbose_formatter.rb +7 -0
  19. data/lib/resque_sqs/log_formatters/very_verbose_formatter.rb +8 -0
  20. data/lib/resque_sqs/logging.rb +18 -0
  21. data/lib/resque_sqs/plugin.rb +66 -0
  22. data/lib/resque_sqs/server/helpers.rb +52 -0
  23. data/lib/resque_sqs/server/public/favicon.ico +0 -0
  24. data/lib/resque_sqs/server/public/idle.png +0 -0
  25. data/lib/resque_sqs/server/public/jquery-1.3.2.min.js +19 -0
  26. data/lib/resque_sqs/server/public/jquery.relatize_date.js +95 -0
  27. data/lib/resque_sqs/server/public/poll.png +0 -0
  28. data/lib/resque_sqs/server/public/ranger.js +78 -0
  29. data/lib/resque_sqs/server/public/reset.css +44 -0
  30. data/lib/resque_sqs/server/public/style.css +91 -0
  31. data/lib/resque_sqs/server/public/working.png +0 -0
  32. data/lib/resque_sqs/server/test_helper.rb +19 -0
  33. data/lib/resque_sqs/server/views/error.erb +1 -0
  34. data/lib/resque_sqs/server/views/failed.erb +29 -0
  35. data/lib/resque_sqs/server/views/failed_job.erb +50 -0
  36. data/lib/resque_sqs/server/views/failed_queues_overview.erb +24 -0
  37. data/lib/resque_sqs/server/views/key_sets.erb +19 -0
  38. data/lib/resque_sqs/server/views/key_string.erb +11 -0
  39. data/lib/resque_sqs/server/views/layout.erb +44 -0
  40. data/lib/resque_sqs/server/views/next_more.erb +22 -0
  41. data/lib/resque_sqs/server/views/overview.erb +4 -0
  42. data/lib/resque_sqs/server/views/queues.erb +58 -0
  43. data/lib/resque_sqs/server/views/stats.erb +62 -0
  44. data/lib/resque_sqs/server/views/workers.erb +109 -0
  45. data/lib/resque_sqs/server/views/working.erb +72 -0
  46. data/lib/resque_sqs/server.rb +271 -0
  47. data/lib/resque_sqs/stat.rb +57 -0
  48. data/lib/resque_sqs/tasks.rb +83 -0
  49. data/lib/resque_sqs/vendor/utf8_util/utf8_util_18.rb +91 -0
  50. data/lib/resque_sqs/vendor/utf8_util/utf8_util_19.rb +5 -0
  51. data/lib/resque_sqs/vendor/utf8_util.rb +20 -0
  52. data/lib/resque_sqs/version.rb +3 -0
  53. data/lib/resque_sqs/worker.rb +779 -0
  54. data/lib/resque_sqs.rb +479 -0
  55. data/lib/tasks/redis_sqs.rake +161 -0
  56. data/lib/tasks/resque_sqs.rake +2 -0
  57. data/test/airbrake_test.rb +27 -0
  58. data/test/failure_base_test.rb +15 -0
  59. data/test/job_hooks_test.rb +465 -0
  60. data/test/job_plugins_test.rb +230 -0
  61. data/test/logging_test.rb +24 -0
  62. data/test/plugin_test.rb +116 -0
  63. data/test/redis-test-cluster.conf +115 -0
  64. data/test/redis-test.conf +115 -0
  65. data/test/resque-web_test.rb +59 -0
  66. data/test/resque_failure_redis_test.rb +19 -0
  67. data/test/resque_hook_test.rb +165 -0
  68. data/test/resque_test.rb +278 -0
  69. data/test/stdout +42 -0
  70. data/test/test_helper.rb +228 -0
  71. data/test/worker_test.rb +1080 -0
  72. metadata +202 -0
@@ -0,0 +1,1080 @@
1
+ require 'test_helper'
2
+ require 'tmpdir'
3
+
4
+ context "ResqueSqs::Worker" do
5
+ setup do
6
+ ResqueSqs.redis.flushall
7
+
8
+ ResqueSqs.before_first_fork = nil
9
+ ResqueSqs.before_fork = nil
10
+ ResqueSqs.after_fork = nil
11
+
12
+ @worker = ResqueSqs::Worker.new(:jobs)
13
+ ResqueSqs::Job.create(:jobs, SomeJob, 20, '/tmp')
14
+ end
15
+
16
+ test "can fail jobs" do
17
+ ResqueSqs::Job.create(:jobs, BadJob)
18
+ @worker.work(0)
19
+ assert_equal 1, ResqueSqs::Failure.count
20
+ end
21
+
22
+ test "failed jobs report exception and message" do
23
+ ResqueSqs::Job.create(:jobs, BadJobWithSyntaxError)
24
+ @worker.work(0)
25
+ assert_equal('SyntaxError', ResqueSqs::Failure.all['exception'])
26
+ assert_equal('Extra Bad job!', ResqueSqs::Failure.all['error'])
27
+ end
28
+
29
+ test "does not allow exceptions from failure backend to escape" do
30
+ job = ResqueSqs::Job.new(:jobs, {})
31
+ with_failure_backend BadFailureBackend do
32
+ @worker.perform job
33
+ end
34
+ end
35
+
36
+ test "does not raise exception for completed jobs" do
37
+ if worker_pid = Kernel.fork
38
+ Process.waitpid(worker_pid)
39
+ assert_equal 0, ResqueSqs::Failure.count
40
+ else
41
+ # ensure we actually fork
42
+ $TESTING = false
43
+ ResqueSqs.redis.client.reconnect
44
+ worker = ResqueSqs::Worker.new(:jobs)
45
+ suppress_warnings do
46
+ worker.work(0)
47
+ end
48
+ exit
49
+ end
50
+ end
51
+
52
+ test "executes at_exit hooks when configured with run_at_exit_hooks" do
53
+ tmpfile = File.join(Dir.tmpdir, "resque_at_exit_test_file")
54
+ FileUtils.rm_f tmpfile
55
+
56
+ if worker_pid = Kernel.fork
57
+ Process.waitpid(worker_pid)
58
+ assert File.exist?(tmpfile), "The file '#{tmpfile}' does not exist"
59
+ assert_equal "at_exit", File.open(tmpfile).read.strip
60
+ else
61
+ # ensure we actually fork
62
+ $TESTING = false
63
+ ResqueSqs.redis.client.reconnect
64
+ ResqueSqs::Job.create(:at_exit_jobs, AtExitJob, tmpfile)
65
+ worker = ResqueSqs::Worker.new(:at_exit_jobs)
66
+ worker.run_at_exit_hooks = true
67
+ suppress_warnings do
68
+ worker.work(0)
69
+ end
70
+ exit
71
+ end
72
+
73
+ end
74
+
75
+ class ::RaiseExceptionOnFailure
76
+
77
+ def self.on_failure_trhow_exception(exception,*args)
78
+ $TESTING = true
79
+ raise "The worker threw an exception"
80
+ end
81
+
82
+ def self.perform
83
+ ""
84
+ end
85
+ end
86
+
87
+ test "should not treat SystemExit as an exception in the child with run_at_exit_hooks == true" do
88
+
89
+ if worker_pid = Kernel.fork
90
+ Process.waitpid(worker_pid)
91
+ else
92
+ # ensure we actually fork
93
+ $TESTING = false
94
+ ResqueSqs.redis.client.reconnect
95
+ ResqueSqs::Job.create(:not_failing_job, RaiseExceptionOnFailure)
96
+ worker = ResqueSqs::Worker.new(:not_failing_job)
97
+ worker.run_at_exit_hooks = true
98
+ suppress_warnings do
99
+ worker.work(0)
100
+ end
101
+ exit
102
+ end
103
+
104
+ end
105
+
106
+
107
+ test "does not execute at_exit hooks by default" do
108
+ tmpfile = File.join(Dir.tmpdir, "resque_at_exit_test_file")
109
+ FileUtils.rm_f tmpfile
110
+
111
+ if worker_pid = Kernel.fork
112
+ Process.waitpid(worker_pid)
113
+ assert !File.exist?(tmpfile), "The file '#{tmpfile}' exists, at_exit hooks were run"
114
+ else
115
+ # ensure we actually fork
116
+ $TESTING = false
117
+ ResqueSqs.redis.client.reconnect
118
+ ResqueSqs::Job.create(:at_exit_jobs, AtExitJob, tmpfile)
119
+ worker = ResqueSqs::Worker.new(:at_exit_jobs)
120
+ suppress_warnings do
121
+ worker.work(0)
122
+ end
123
+ exit
124
+ end
125
+
126
+ end
127
+
128
+ test "does report failure for jobs with invalid payload" do
129
+ job = ResqueSqs::Job.new(:jobs, { 'class' => 'NotAValidJobClass', 'args' => '' })
130
+ @worker.perform job
131
+ assert_equal 1, ResqueSqs::Failure.count, 'failure not reported'
132
+ end
133
+
134
+ test "register 'run_at' time on UTC timezone in ISO8601 format" do
135
+ job = ResqueSqs::Job.new(:jobs, {'class' => 'GoodJob', 'args' => "blah"})
136
+ now = Time.now.utc.iso8601
137
+ @worker.working_on(job)
138
+ assert_equal now, @worker.processing['run_at']
139
+ end
140
+
141
+ test "fails uncompleted jobs with DirtyExit by default on exit" do
142
+ job = ResqueSqs::Job.new(:jobs, {'class' => 'GoodJob', 'args' => "blah"})
143
+ @worker.working_on(job)
144
+ @worker.unregister_worker
145
+ assert_equal 1, ResqueSqs::Failure.count
146
+ assert_equal('ResqueSqs::DirtyExit', ResqueSqs::Failure.all['exception'])
147
+ end
148
+
149
+ test "fails uncompleted jobs with worker exception on exit" do
150
+ job = ResqueSqs::Job.new(:jobs, {'class' => 'GoodJob', 'args' => "blah"})
151
+ @worker.working_on(job)
152
+ @worker.unregister_worker(StandardError.new)
153
+ assert_equal 1, ResqueSqs::Failure.count
154
+ assert_equal('StandardError', ResqueSqs::Failure.all['exception'])
155
+ end
156
+
157
+ class ::SimpleJobWithFailureHandling
158
+ def self.on_failure_record_failure(exception, *job_args)
159
+ @@exception = exception
160
+ end
161
+
162
+ def self.exception
163
+ @@exception
164
+ end
165
+ end
166
+
167
+ test "fails uncompleted jobs on exit, and calls failure hook" do
168
+ job = ResqueSqs::Job.new(:jobs, {'class' => 'SimpleJobWithFailureHandling', 'args' => ""})
169
+ @worker.working_on(job)
170
+ @worker.unregister_worker
171
+ assert_equal 1, ResqueSqs::Failure.count
172
+ assert(SimpleJobWithFailureHandling.exception.kind_of?(ResqueSqs::DirtyExit))
173
+ end
174
+
175
+ class ::SimpleFailingJob
176
+ @@exception_count = 0
177
+
178
+ def self.on_failure_record_failure(exception, *job_args)
179
+ @@exception_count += 1
180
+ end
181
+
182
+ def self.exception_count
183
+ @@exception_count
184
+ end
185
+
186
+ def self.perform
187
+ raise Exception.new
188
+ end
189
+ end
190
+
191
+ test "only calls failure hook once on exception" do
192
+ job = ResqueSqs::Job.new(:jobs, {'class' => 'SimpleFailingJob', 'args' => ""})
193
+ @worker.perform(job)
194
+ assert_equal 1, ResqueSqs::Failure.count
195
+ assert_equal 1, SimpleFailingJob.exception_count
196
+ end
197
+
198
+ test "can peek at failed jobs" do
199
+ 10.times { ResqueSqs::Job.create(:jobs, BadJob) }
200
+ @worker.work(0)
201
+ assert_equal 10, ResqueSqs::Failure.count
202
+
203
+ assert_equal 10, ResqueSqs::Failure.all(0, 20).size
204
+ end
205
+
206
+ test "can clear failed jobs" do
207
+ ResqueSqs::Job.create(:jobs, BadJob)
208
+ @worker.work(0)
209
+ assert_equal 1, ResqueSqs::Failure.count
210
+ ResqueSqs::Failure.clear
211
+ assert_equal 0, ResqueSqs::Failure.count
212
+ end
213
+
214
+ test "catches exceptional jobs" do
215
+ ResqueSqs::Job.create(:jobs, BadJob)
216
+ ResqueSqs::Job.create(:jobs, BadJob)
217
+ @worker.process
218
+ @worker.process
219
+ @worker.process
220
+ assert_equal 2, ResqueSqs::Failure.count
221
+ end
222
+
223
+ test "strips whitespace from queue names" do
224
+ queues = "critical, high, low".split(',')
225
+ worker = ResqueSqs::Worker.new(*queues)
226
+ assert_equal %w( critical high low ), worker.queues
227
+ end
228
+
229
+ test "can work on multiple queues" do
230
+ ResqueSqs::Job.create(:high, GoodJob)
231
+ ResqueSqs::Job.create(:critical, GoodJob)
232
+
233
+ worker = ResqueSqs::Worker.new(:critical, :high)
234
+
235
+ worker.process
236
+ assert_equal 1, ResqueSqs.size(:high)
237
+ assert_equal 0, ResqueSqs.size(:critical)
238
+
239
+ worker.process
240
+ assert_equal 0, ResqueSqs.size(:high)
241
+ end
242
+
243
+ test "can work on all queues" do
244
+ ResqueSqs::Job.create(:high, GoodJob)
245
+ ResqueSqs::Job.create(:critical, GoodJob)
246
+ ResqueSqs::Job.create(:blahblah, GoodJob)
247
+
248
+ worker = ResqueSqs::Worker.new("*")
249
+
250
+ worker.work(0)
251
+ assert_equal 0, ResqueSqs.size(:high)
252
+ assert_equal 0, ResqueSqs.size(:critical)
253
+ assert_equal 0, ResqueSqs.size(:blahblah)
254
+ end
255
+
256
+ test "can work with wildcard at the end of the list" do
257
+ ResqueSqs::Job.create(:high, GoodJob)
258
+ ResqueSqs::Job.create(:critical, GoodJob)
259
+ ResqueSqs::Job.create(:blahblah, GoodJob)
260
+ ResqueSqs::Job.create(:beer, GoodJob)
261
+
262
+ worker = ResqueSqs::Worker.new(:critical, :high, "*")
263
+
264
+ worker.work(0)
265
+ assert_equal 0, ResqueSqs.size(:high)
266
+ assert_equal 0, ResqueSqs.size(:critical)
267
+ assert_equal 0, ResqueSqs.size(:blahblah)
268
+ assert_equal 0, ResqueSqs.size(:beer)
269
+ end
270
+
271
+ test "can work with wildcard at the middle of the list" do
272
+ ResqueSqs::Job.create(:high, GoodJob)
273
+ ResqueSqs::Job.create(:critical, GoodJob)
274
+ ResqueSqs::Job.create(:blahblah, GoodJob)
275
+ ResqueSqs::Job.create(:beer, GoodJob)
276
+
277
+ worker = ResqueSqs::Worker.new(:critical, "*", :high)
278
+
279
+ worker.work(0)
280
+ assert_equal 0, ResqueSqs.size(:high)
281
+ assert_equal 0, ResqueSqs.size(:critical)
282
+ assert_equal 0, ResqueSqs.size(:blahblah)
283
+ assert_equal 0, ResqueSqs.size(:beer)
284
+ end
285
+
286
+ test "processes * queues in alphabetical order" do
287
+ ResqueSqs::Job.create(:high, GoodJob)
288
+ ResqueSqs::Job.create(:critical, GoodJob)
289
+ ResqueSqs::Job.create(:blahblah, GoodJob)
290
+
291
+ worker = ResqueSqs::Worker.new("*")
292
+ processed_queues = []
293
+
294
+ worker.work(0) do |job|
295
+ processed_queues << job.queue
296
+ end
297
+
298
+ assert_equal %w( jobs high critical blahblah ).sort, processed_queues
299
+ end
300
+
301
+ test "works with globs" do
302
+ ResqueSqs::Job.create(:critical, GoodJob)
303
+ ResqueSqs::Job.create(:test_one, GoodJob)
304
+ ResqueSqs::Job.create(:test_two, GoodJob)
305
+
306
+ worker = ResqueSqs::Worker.new("test_*")
307
+
308
+ worker.work(0)
309
+ assert_equal 1, ResqueSqs.size(:critical)
310
+ assert_equal 0, ResqueSqs.size(:test_one)
311
+ assert_equal 0, ResqueSqs.size(:test_two)
312
+ end
313
+
314
+ test "has a unique id" do
315
+ assert_equal "#{`hostname`.chomp}:#{$$}:jobs", @worker.to_s
316
+ end
317
+
318
+ test "complains if no queues are given" do
319
+ assert_raise ResqueSqs::NoQueueError do
320
+ ResqueSqs::Worker.new
321
+ end
322
+ end
323
+
324
+ test "fails if a job class has no `perform` method" do
325
+ worker = ResqueSqs::Worker.new(:perform_less)
326
+ ResqueSqs::Job.create(:perform_less, Object)
327
+
328
+ assert_equal 0, ResqueSqs::Failure.count
329
+ worker.work(0)
330
+ assert_equal 1, ResqueSqs::Failure.count
331
+ end
332
+
333
+ test "inserts itself into the 'workers' list on startup" do
334
+ @worker.extend(AssertInWorkBlock).work(0) do
335
+ assert_equal @worker, ResqueSqs.workers[0]
336
+ end
337
+ end
338
+
339
+ test "removes itself from the 'workers' list on shutdown" do
340
+ @worker.extend(AssertInWorkBlock).work(0) do
341
+ assert_equal @worker, ResqueSqs.workers[0]
342
+ end
343
+
344
+ assert_equal [], ResqueSqs.workers
345
+ end
346
+
347
+ test "removes worker with stringified id" do
348
+ @worker.extend(AssertInWorkBlock).work(0) do
349
+ worker_id = ResqueSqs.workers[0].to_s
350
+ ResqueSqs.remove_worker(worker_id)
351
+ assert_equal [], ResqueSqs.workers
352
+ end
353
+ end
354
+
355
+ test "records what it is working on" do
356
+ @worker.extend(AssertInWorkBlock).work(0) do
357
+ task = @worker.job
358
+ assert_equal({"args"=>[20, "/tmp"], "class"=>"SomeJob"}, task['payload'])
359
+ assert task['run_at']
360
+ assert_equal 'jobs', task['queue']
361
+ end
362
+ end
363
+
364
+ test "clears its status when not working on anything" do
365
+ @worker.work(0)
366
+ assert_equal Hash.new, @worker.job
367
+ end
368
+
369
+ test "knows when it is working" do
370
+ @worker.extend(AssertInWorkBlock).work(0) do
371
+ assert @worker.working?
372
+ end
373
+ end
374
+
375
+ test "knows when it is idle" do
376
+ @worker.work(0)
377
+ assert @worker.idle?
378
+ end
379
+
380
+ test "knows who is working" do
381
+ @worker.extend(AssertInWorkBlock).work(0) do
382
+ assert_equal [@worker], ResqueSqs.working
383
+ end
384
+ end
385
+
386
+ test "keeps track of how many jobs it has processed" do
387
+ ResqueSqs::Job.create(:jobs, BadJob)
388
+ ResqueSqs::Job.create(:jobs, BadJob)
389
+
390
+ 3.times do
391
+ job = @worker.reserve
392
+ @worker.process job
393
+ end
394
+ assert_equal 3, @worker.processed
395
+ end
396
+
397
+ test "keeps track of how many failures it has seen" do
398
+ ResqueSqs::Job.create(:jobs, BadJob)
399
+ ResqueSqs::Job.create(:jobs, BadJob)
400
+
401
+ 3.times do
402
+ job = @worker.reserve
403
+ @worker.process job
404
+ end
405
+ assert_equal 2, @worker.failed
406
+ end
407
+
408
+ test "stats are erased when the worker goes away" do
409
+ @worker.work(0)
410
+ assert_equal 0, @worker.processed
411
+ assert_equal 0, @worker.failed
412
+ end
413
+
414
+ test "knows when it started" do
415
+ time = Time.now
416
+ @worker.extend(AssertInWorkBlock).work(0) do
417
+ assert Time.parse(@worker.started) - time < 0.1
418
+ end
419
+ end
420
+
421
+ test "knows whether it exists or not" do
422
+ @worker.extend(AssertInWorkBlock).work(0) do
423
+ assert ResqueSqs::Worker.exists?(@worker)
424
+ assert !ResqueSqs::Worker.exists?('blah-blah')
425
+ end
426
+ end
427
+
428
+ test "sets $0 while working" do
429
+ @worker.extend(AssertInWorkBlock).work(0) do
430
+ ver = ResqueSqs::Version
431
+ assert_equal "resque-#{ver}: Processing jobs since #{Time.now.to_i} [SomeJob]", $0
432
+ end
433
+ end
434
+
435
+ test "can be found" do
436
+ @worker.extend(AssertInWorkBlock).work(0) do
437
+ found = ResqueSqs::Worker.find(@worker.to_s)
438
+ assert_equal @worker.to_s, found.to_s
439
+ assert found.working?
440
+ assert_equal @worker.job, found.job
441
+ end
442
+ end
443
+
444
+ test "doesn't find fakes" do
445
+ @worker.extend(AssertInWorkBlock).work(0) do
446
+ found = ResqueSqs::Worker.find('blah-blah')
447
+ assert_equal nil, found
448
+ end
449
+ end
450
+
451
+ test "cleans up dead worker info on start (crash recovery)" do
452
+ # first we fake out several dead workers
453
+ # 1: matches queue and hostname; gets pruned.
454
+ workerA = ResqueSqs::Worker.new(:jobs)
455
+ workerA.instance_variable_set(:@to_s, "#{`hostname`.chomp}:1:jobs")
456
+ workerA.register_worker
457
+
458
+ # 2. matches queue but not hostname; no prune.
459
+ workerB = ResqueSqs::Worker.new(:jobs)
460
+ workerB.instance_variable_set(:@to_s, "#{`hostname`.chomp}-foo:2:jobs")
461
+ workerB.register_worker
462
+
463
+ # 3. matches hostname but not queue; no prune.
464
+ workerB = ResqueSqs::Worker.new(:high)
465
+ workerB.instance_variable_set(:@to_s, "#{`hostname`.chomp}:3:high")
466
+ workerB.register_worker
467
+
468
+ # 4. matches neither hostname nor queue; no prune.
469
+ workerB = ResqueSqs::Worker.new(:high)
470
+ workerB.instance_variable_set(:@to_s, "#{`hostname`.chomp}-foo:4:high")
471
+ workerB.register_worker
472
+
473
+ assert_equal 4, ResqueSqs.workers.size
474
+
475
+ # then we prune them
476
+ @worker.work(0)
477
+
478
+ worker_strings = ResqueSqs::Worker.all.map(&:to_s)
479
+
480
+ assert_equal 3, ResqueSqs.workers.size
481
+
482
+ # pruned
483
+ assert !worker_strings.include?("#{`hostname`.chomp}:1:jobs")
484
+
485
+ # not pruned
486
+ assert worker_strings.include?("#{`hostname`.chomp}-foo:2:jobs")
487
+ assert worker_strings.include?("#{`hostname`.chomp}:3:high")
488
+ assert worker_strings.include?("#{`hostname`.chomp}-foo:4:high")
489
+ end
490
+
491
+ test "worker_pids returns pids" do
492
+ known_workers = @worker.worker_pids
493
+ assert !known_workers.empty?
494
+ end
495
+
496
+ test "Processed jobs count" do
497
+ @worker.work(0)
498
+ assert_equal 1, ResqueSqs.info[:processed]
499
+ end
500
+
501
+ test "Will call a before_first_fork hook only once" do
502
+ ResqueSqs.redis.flushall
503
+ $BEFORE_FORK_CALLED = 0
504
+ ResqueSqs.before_first_fork = Proc.new { $BEFORE_FORK_CALLED += 1 }
505
+ workerA = ResqueSqs::Worker.new(:jobs)
506
+ ResqueSqs::Job.create(:jobs, SomeJob, 20, '/tmp')
507
+
508
+ assert_equal 0, $BEFORE_FORK_CALLED
509
+
510
+ workerA.work(0)
511
+ assert_equal 1, $BEFORE_FORK_CALLED
512
+
513
+ # TODO: Verify it's only run once. Not easy.
514
+ # workerA.work(0)
515
+ # assert_equal 1, $BEFORE_FORK_CALLED
516
+ end
517
+
518
+ test "Will call a before_fork hook before forking" do
519
+ ResqueSqs.redis.flushall
520
+ $BEFORE_FORK_CALLED = false
521
+ ResqueSqs.before_fork = Proc.new { $BEFORE_FORK_CALLED = true }
522
+ workerA = ResqueSqs::Worker.new(:jobs)
523
+
524
+ assert !$BEFORE_FORK_CALLED
525
+ ResqueSqs::Job.create(:jobs, SomeJob, 20, '/tmp')
526
+ workerA.work(0)
527
+ assert $BEFORE_FORK_CALLED
528
+ end
529
+
530
+ test "Will not call a before_fork hook when the worker can't fork" do
531
+ ResqueSqs.redis.flushall
532
+ $BEFORE_FORK_CALLED = false
533
+ ResqueSqs.before_fork = Proc.new { $BEFORE_FORK_CALLED = true }
534
+ workerA = ResqueSqs::Worker.new(:jobs)
535
+ workerA.cant_fork = true
536
+
537
+ assert !$BEFORE_FORK_CALLED, "before_fork should not have been called before job runs"
538
+ ResqueSqs::Job.create(:jobs, SomeJob, 20, '/tmp')
539
+ workerA.work(0)
540
+ assert !$BEFORE_FORK_CALLED, "before_fork should not have been called after job runs"
541
+ end
542
+
543
+ test "setting verbose to true" do
544
+ @worker.verbose = true
545
+
546
+ assert @worker.verbose
547
+ assert !@worker.very_verbose
548
+ end
549
+
550
+ test "setting verbose to false" do
551
+ @worker.verbose = false
552
+
553
+ assert !@worker.verbose
554
+ assert !@worker.very_verbose
555
+ end
556
+
557
+ test "setting very_verbose to true" do
558
+ @worker.very_verbose = true
559
+
560
+ assert !@worker.verbose
561
+ assert @worker.very_verbose
562
+ end
563
+
564
+ test "setting setting verbose to true and then very_verbose to false" do
565
+ @worker.very_verbose = true
566
+ @worker.verbose = true
567
+ @worker.very_verbose = false
568
+
569
+ assert @worker.verbose
570
+ assert !@worker.very_verbose
571
+ end
572
+
573
+ test "verbose prints out logs" do
574
+ messages = StringIO.new
575
+ ResqueSqs.logger = Logger.new(messages)
576
+ @worker.verbose = true
577
+
578
+ begin
579
+ @worker.log("omghi mom")
580
+ ensure
581
+ reset_logger
582
+ end
583
+
584
+ assert_equal "*** omghi mom\n", messages.string
585
+ end
586
+
587
+ test "unsetting verbose works" do
588
+ messages = StringIO.new
589
+ ResqueSqs.logger = Logger.new(messages)
590
+ @worker.verbose = true
591
+ @worker.verbose = false
592
+
593
+ begin
594
+ @worker.log("omghi mom")
595
+ ensure
596
+ reset_logger
597
+ end
598
+
599
+ assert_equal "", messages.string
600
+ end
601
+
602
+ test "very verbose works in the afternoon" do
603
+ messages = StringIO.new
604
+ ResqueSqs.logger = Logger.new(messages)
605
+
606
+ begin
607
+ require 'time'
608
+ last_puts = ""
609
+ Time.fake_time = Time.parse("15:44:33 2011-03-02")
610
+
611
+ @worker.very_verbose = true
612
+ @worker.log("some log text")
613
+
614
+ assert_match /\*\* \[15:44:33 2011-03-02\] \d+: some log text/, messages.string
615
+ ensure
616
+ Time.fake_time = nil
617
+ reset_logger
618
+ end
619
+ end
620
+
621
+ test "won't fork if ENV['FORK_PER_JOB'] is false" do
622
+ begin
623
+ $TESTING = false
624
+ workerA = ResqueSqs::Worker.new(:jobs)
625
+
626
+ if workerA.will_fork?
627
+ begin
628
+ ENV["FORK_PER_JOB"] = 'false'
629
+ assert !workerA.will_fork?
630
+ ensure
631
+ ENV["FORK_PER_JOB"] = 'true'
632
+ end
633
+ end
634
+ ensure
635
+ $TESTING = true
636
+ end
637
+ end
638
+
639
+ test "Will call an after_fork hook if we're forking" do
640
+ ResqueSqs.redis.flushall
641
+ $AFTER_FORK_CALLED = false
642
+ ResqueSqs.after_fork = Proc.new { $AFTER_FORK_CALLED = true }
643
+ workerA = ResqueSqs::Worker.new(:jobs)
644
+
645
+ assert !$AFTER_FORK_CALLED
646
+ ResqueSqs::Job.create(:jobs, SomeJob, 20, '/tmp')
647
+ workerA.work(0)
648
+ assert $AFTER_FORK_CALLED == workerA.will_fork?
649
+ end
650
+
651
+ test "Will not call an after_fork hook when the worker can't fork" do
652
+ ResqueSqs.redis.flushall
653
+ $AFTER_FORK_CALLED = false
654
+ ResqueSqs.after_fork = Proc.new { $AFTER_FORK_CALLED = true }
655
+ workerA = ResqueSqs::Worker.new(:jobs)
656
+ workerA.cant_fork = true
657
+
658
+ assert !$AFTER_FORK_CALLED
659
+ ResqueSqs::Job.create(:jobs, SomeJob, 20, '/tmp')
660
+ workerA.work(0)
661
+ assert !$AFTER_FORK_CALLED
662
+ end
663
+
664
+ test "returns PID of running process" do
665
+ assert_equal @worker.to_s.split(":")[1].to_i, @worker.pid
666
+ end
667
+
668
+ test "requeue failed queue" do
669
+ queue = 'good_job'
670
+ ResqueSqs::Failure.create(:exception => Exception.new, :worker => ResqueSqs::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
671
+ ResqueSqs::Failure.create(:exception => Exception.new, :worker => ResqueSqs::Worker.new(queue), :queue => 'some_job', :payload => {'class' => 'SomeJob'})
672
+ ResqueSqs::Failure.requeue_queue(queue)
673
+ assert ResqueSqs::Failure.all(0).has_key?('retried_at')
674
+ assert !ResqueSqs::Failure.all(1).has_key?('retried_at')
675
+ end
676
+
677
+ test "remove failed queue" do
678
+ queue = 'good_job'
679
+ queue2 = 'some_job'
680
+ ResqueSqs::Failure.create(:exception => Exception.new, :worker => ResqueSqs::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
681
+ ResqueSqs::Failure.create(:exception => Exception.new, :worker => ResqueSqs::Worker.new(queue2), :queue => queue2, :payload => {'class' => 'SomeJob'})
682
+ ResqueSqs::Failure.create(:exception => Exception.new, :worker => ResqueSqs::Worker.new(queue), :queue => queue, :payload => {'class' => 'GoodJob'})
683
+ ResqueSqs::Failure.remove_queue(queue)
684
+ assert_equal queue2, ResqueSqs::Failure.all(0)['queue']
685
+ assert_equal 1, ResqueSqs::Failure.count
686
+ end
687
+
688
+ test "reconnects to redis after fork" do
689
+ original_connection = ResqueSqs.redis.client.connection.instance_variable_get("@sock")
690
+ @worker.work(0)
691
+ assert_not_equal original_connection, ResqueSqs.redis.client.connection.instance_variable_get("@sock")
692
+ end
693
+
694
+ test "tries to reconnect three times before giving up and the failure does not unregister the parent" do
695
+ begin
696
+ class Redis::Client
697
+ alias_method :original_reconnect, :reconnect
698
+
699
+ def reconnect
700
+ raise Redis::BaseConnectionError
701
+ end
702
+ end
703
+
704
+ class ResqueSqs::Worker
705
+ alias_method :original_sleep, :sleep
706
+
707
+ def sleep(duration = nil)
708
+ # noop
709
+ end
710
+ end
711
+
712
+ stdout, stderr = capture_io do
713
+ ResqueSqs.logger = Logger.new($stdout)
714
+ @worker.work(0)
715
+ end
716
+
717
+ assert_equal 3, stdout.scan(/retrying/).count
718
+ assert_equal 1, stdout.scan(/quitting/).count
719
+ assert_equal 0, stdout.scan(/Failed to start worker/).count
720
+ assert_equal 1, stdout.scan(/Redis::BaseConnectionError: Redis::BaseConnectionError/).count
721
+
722
+ ensure
723
+ class Redis::Client
724
+ alias_method :reconnect, :original_reconnect
725
+ end
726
+
727
+ class ResqueSqs::Worker
728
+ alias_method :sleep, :original_sleep
729
+ end
730
+ end
731
+ end
732
+
733
+ test "will call before_pause before it is paused" do
734
+ before_pause_called = false
735
+ captured_worker = nil
736
+ begin
737
+ class Redis::Client
738
+ alias_method :original_reconnect, :reconnect
739
+
740
+ def reconnect
741
+ raise Redis::BaseConnectionError
742
+ end
743
+ end
744
+
745
+ class ResqueSqs::Worker
746
+ alias_method :original_sleep, :sleep
747
+
748
+ def sleep(duration = nil)
749
+ # noop
750
+ end
751
+ end
752
+
753
+ class DummyLogger
754
+ attr_reader :messages
755
+
756
+ def initialize
757
+ @messages = []
758
+ end
759
+
760
+ def info(message); @messages << message; end
761
+ alias_method :debug, :info
762
+ alias_method :warn, :info
763
+ alias_method :error, :info
764
+ alias_method :fatal, :info
765
+ end
766
+
767
+ ResqueSqs.logger = DummyLogger.new
768
+ begin
769
+ @worker.work(0)
770
+ messages = ResqueSqs.logger.messages
771
+ ensure
772
+ reset_logger
773
+ end
774
+
775
+ assert_equal 3, messages.grep(/retrying/).count
776
+ assert_equal 1, messages.grep(/quitting/).count
777
+ ensure
778
+ class Redis::Client
779
+ alias_method :reconnect, :original_reconnect
780
+ end
781
+
782
+ class ResqueSqs::Worker
783
+ alias_method :sleep, :original_sleep
784
+ end
785
+ end
786
+ end
787
+
788
+ if !defined?(RUBY_ENGINE) || defined?(RUBY_ENGINE) && RUBY_ENGINE != "jruby"
789
+ test "old signal handling is the default" do
790
+ rescue_time = nil
791
+
792
+ begin
793
+ class LongRunningJob
794
+ @queue = :long_running_job
795
+
796
+ def self.perform( run_time, rescue_time=nil )
797
+ ResqueSqs.redis.client.reconnect # get its own connection
798
+ ResqueSqs.redis.rpush( 'sigterm-test:start', Process.pid )
799
+ sleep run_time
800
+ ResqueSqs.redis.rpush( 'sigterm-test:result', 'Finished Normally' )
801
+ rescue ResqueSqs::TermException => e
802
+ ResqueSqs.redis.rpush( 'sigterm-test:result', %Q(Caught SignalException: #{e.inspect}))
803
+ sleep rescue_time unless rescue_time.nil?
804
+ ensure
805
+ puts 'fuuuu'
806
+ ResqueSqs.redis.rpush( 'sigterm-test:final', 'exiting.' )
807
+ end
808
+ end
809
+
810
+ ResqueSqs.enqueue( LongRunningJob, 5, rescue_time )
811
+
812
+ worker_pid = Kernel.fork do
813
+ # ensure we actually fork
814
+ $TESTING = false
815
+ # reconnect since we just forked
816
+ ResqueSqs.redis.client.reconnect
817
+
818
+ worker = ResqueSqs::Worker.new(:long_running_job)
819
+
820
+ suppress_warnings do
821
+ worker.work(0)
822
+ end
823
+ exit!
824
+ end
825
+
826
+ # ensure the worker is started
827
+ start_status = ResqueSqs.redis.blpop( 'sigterm-test:start', 5 )
828
+ assert_not_nil start_status
829
+ child_pid = start_status[1].to_i
830
+ assert_operator child_pid, :>, 0
831
+
832
+ # send signal to abort the worker
833
+ Process.kill('TERM', worker_pid)
834
+ Process.waitpid(worker_pid)
835
+
836
+ # wait to see how it all came down
837
+ result = ResqueSqs.redis.blpop( 'sigterm-test:result', 5 )
838
+ assert_nil result
839
+
840
+ # ensure that the child pid is no longer running
841
+ child_not_running = `ps -p #{child_pid.to_s} -o pid=`.empty?
842
+ assert child_not_running
843
+ ensure
844
+ remaining_keys = ResqueSqs.redis.keys('sigterm-test:*') || []
845
+ ResqueSqs.redis.del(*remaining_keys) unless remaining_keys.empty?
846
+ end
847
+ end
848
+ end
849
+
850
+ if !defined?(RUBY_ENGINE) || defined?(RUBY_ENGINE) && RUBY_ENGINE != "jruby"
851
+ [SignalException, ResqueSqs::TermException].each do |exception|
852
+ {
853
+ 'cleanup occurs in allotted time' => nil,
854
+ 'cleanup takes too long' => 2
855
+ }.each do |scenario,rescue_time|
856
+ test "SIGTERM when #{scenario} while catching #{exception}" do
857
+ begin
858
+ eval("class LongRunningJob; @@exception = #{exception}; end")
859
+ class LongRunningJob
860
+ @queue = :long_running_job
861
+
862
+ def self.perform( run_time, rescue_time=nil )
863
+ ResqueSqs.redis.client.reconnect # get its own connection
864
+ ResqueSqs.redis.rpush( 'sigterm-test:start', Process.pid )
865
+ sleep run_time
866
+ ResqueSqs.redis.rpush( 'sigterm-test:result', 'Finished Normally' )
867
+ rescue @@exception => e
868
+ ResqueSqs.redis.rpush( 'sigterm-test:result', %Q(Caught SignalException: #{e.inspect}))
869
+ sleep rescue_time unless rescue_time.nil?
870
+ ensure
871
+ ResqueSqs.redis.rpush( 'sigterm-test:final', 'exiting.' )
872
+ end
873
+ end
874
+
875
+ ResqueSqs.enqueue( LongRunningJob, 5, rescue_time )
876
+
877
+ worker_pid = Kernel.fork do
878
+ # ensure we actually fork
879
+ $TESTING = false
880
+ # reconnect since we just forked
881
+ ResqueSqs.redis.client.reconnect
882
+
883
+ worker = ResqueSqs::Worker.new(:long_running_job)
884
+ worker.term_timeout = 1
885
+ worker.term_child = 1
886
+
887
+ worker.work(0)
888
+ exit!
889
+ end
890
+
891
+ # ensure the worker is started
892
+ start_status = ResqueSqs.redis.blpop( 'sigterm-test:start', 5 )
893
+ assert_not_nil start_status
894
+ child_pid = start_status[1].to_i
895
+ assert_operator child_pid, :>, 0
896
+
897
+ # send signal to abort the worker
898
+ Process.kill('TERM', worker_pid)
899
+ Process.waitpid(worker_pid)
900
+
901
+ # wait to see how it all came down
902
+ result = ResqueSqs.redis.blpop( 'sigterm-test:result', 5 )
903
+ assert_not_nil result
904
+ assert !result[1].start_with?('Finished Normally'), 'Job Finished normally. Sleep not long enough?'
905
+ assert result[1].start_with? 'Caught SignalException', 'Signal exception not raised in child.'
906
+
907
+ # ensure that the child pid is no longer running
908
+ child_still_running = !(`ps -p #{child_pid.to_s} -o pid=`).empty?
909
+ assert !child_still_running
910
+
911
+ # see if post-cleanup occurred. This should happen IFF the rescue_time is less than the term_timeout
912
+ post_cleanup_occurred = ResqueSqs.redis.lpop( 'sigterm-test:final' )
913
+ assert post_cleanup_occurred, 'post cleanup did not occur. SIGKILL sent too early?' if rescue_time.nil?
914
+ assert !post_cleanup_occurred, 'post cleanup occurred. SIGKILL sent too late?' unless rescue_time.nil?
915
+
916
+ ensure
917
+ remaining_keys = ResqueSqs.redis.keys('sigterm-test:*') || []
918
+ ResqueSqs.redis.del(*remaining_keys) unless remaining_keys.empty?
919
+ end
920
+ end
921
+ end
922
+ end
923
+
924
+ test "exits with ResqueSqs::TermException when using TERM_CHILD and not forking" do
925
+ begin
926
+ class LongRunningJob
927
+ @queue = :long_running_job
928
+
929
+ def self.perform(run_time)
930
+ ResqueSqs.redis.client.reconnect # get its own connection
931
+ ResqueSqs.redis.rpush('term-exception-test:start', Process.pid)
932
+ sleep run_time
933
+ ResqueSqs.redis.rpush('term-exception-test:result', 'Finished Normally')
934
+ rescue ResqueSqs::TermException => e
935
+ ResqueSqs.redis.rpush('term-exception-test:result', %Q(Caught TermException: #{e.inspect}))
936
+ ensure
937
+ ResqueSqs.redis.rpush('term-exception-test:final', 'exiting.')
938
+ end
939
+ end
940
+
941
+ ResqueSqs.enqueue(LongRunningJob, 5)
942
+
943
+ worker_pid = Kernel.fork do
944
+ # reconnect to redis
945
+ ResqueSqs.redis.client.reconnect
946
+
947
+ # ensure we don't fork (in worker)
948
+ $TESTING = false
949
+ ENV['FORK_PER_JOB'] = 'false'
950
+
951
+ worker = ResqueSqs::Worker.new(:long_running_job)
952
+ worker.term_timeout = 1
953
+ worker.term_child = 1
954
+
955
+ worker.work(0)
956
+ exit!
957
+ end
958
+
959
+ # ensure the worker is started
960
+ start_status = ResqueSqs.redis.blpop('term-exception-test:start', 5)
961
+ assert_not_nil start_status
962
+ child_pid = start_status[1].to_i
963
+ assert_operator child_pid, :>, 0
964
+
965
+ # send signal to abort the worker
966
+ Process.kill('TERM', worker_pid)
967
+ Process.waitpid(worker_pid)
968
+
969
+ # wait to see how it all came down
970
+ result = ResqueSqs.redis.blpop('term-exception-test:result', 5)
971
+ assert_not_nil result
972
+ assert !result[1].start_with?('Finished Normally'), 'Job finished normally. Sleep not long enough?'
973
+ assert result[1].start_with?('Caught TermException'), 'TermException not raised in child.'
974
+
975
+ # ensure that the child pid is no longer running
976
+ child_still_running = !(`ps -p #{child_pid.to_s} -o pid=`).empty?
977
+ assert !child_still_running
978
+
979
+ # see if post-cleanup occurred.
980
+ post_cleanup_occurred = ResqueSqs.redis.lpop( 'term-exception-test:final' )
981
+ assert post_cleanup_occurred, 'post cleanup did not occur. SIGKILL sent too early?'
982
+
983
+ ensure
984
+ remaining_keys = ResqueSqs.redis.keys('term-exception-test:*') || []
985
+ ResqueSqs.redis.del(*remaining_keys) unless remaining_keys.empty?
986
+ end
987
+ end
988
+
989
+ test "displays warning when not using term_child" do
990
+ begin
991
+ $TESTING = false
992
+ stdout, stderr = capture_io { @worker.work(0) }
993
+
994
+ assert stderr.match(/^WARNING:/)
995
+ ensure
996
+ $TESTING = true
997
+ end
998
+ end
999
+
1000
+ test "it does not display warning when using term_child" do
1001
+ @worker.term_child = "1"
1002
+ stdout, stderr = capture_io { @worker.work(0) }
1003
+
1004
+ assert !stderr.match(/^WARNING:/)
1005
+ end
1006
+
1007
+ class SuicidalJob
1008
+ @queue = :jobs
1009
+
1010
+ def self.perform
1011
+ Process.kill('KILL', Process.pid)
1012
+ end
1013
+
1014
+ def self.on_failure_store_exception(exc, *args)
1015
+ @@failure_exception = exc
1016
+ end
1017
+ end
1018
+
1019
+ test "will notify failure hooks when a job is killed by a signal" do
1020
+ begin
1021
+ $TESTING = false
1022
+ ResqueSqs.enqueue(SuicidalJob)
1023
+ suppress_warnings do
1024
+ @worker.work(0)
1025
+ end
1026
+ assert_equal ResqueSqs::DirtyExit, SuicidalJob.send(:class_variable_get, :@@failure_exception).class
1027
+ ensure
1028
+ $TESTING = true
1029
+ end
1030
+ end
1031
+ end
1032
+
1033
+ test "displays warning when using verbose" do
1034
+ begin
1035
+ $TESTING = false
1036
+ stdout, stderr = capture_io { @worker.verbose }
1037
+ ensure
1038
+ $TESTING = true
1039
+ end
1040
+ $warned_logger_severity_deprecation = false
1041
+
1042
+ assert stderr.match(/WARNING:/)
1043
+ end
1044
+
1045
+ test "displays warning when using verbose=" do
1046
+ begin
1047
+ $TESTING = false
1048
+ stdout, stderr = capture_io { @worker.verbose = true }
1049
+ ensure
1050
+ $TESTING = true
1051
+ end
1052
+ $warned_logger_severity_deprecation = false
1053
+
1054
+ assert stderr.match(/WARNING:/)
1055
+ end
1056
+
1057
+ test "displays warning when using very_verbose" do
1058
+ begin
1059
+ $TESTING = false
1060
+ stdout, stderr = capture_io { @worker.very_verbose }
1061
+ ensure
1062
+ $TESTING = true
1063
+ end
1064
+ $warned_logger_severity_deprecation = false
1065
+
1066
+ assert stderr.match(/WARNING:/)
1067
+ end
1068
+
1069
+ test "displays warning when using very_verbose=" do
1070
+ begin
1071
+ $TESTING = false
1072
+ stdout, stderr = capture_io { @worker.very_verbose = true }
1073
+ ensure
1074
+ $TESTING = true
1075
+ end
1076
+ $warned_logger_severity_deprecation = false
1077
+
1078
+ assert stderr.match(/WARNING:/)
1079
+ end
1080
+ end