que 0.14.3 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +108 -14
  4. data/LICENSE.txt +1 -1
  5. data/README.md +49 -45
  6. data/bin/command_line_interface.rb +239 -0
  7. data/bin/que +8 -82
  8. data/docs/README.md +2 -0
  9. data/docs/active_job.md +6 -0
  10. data/docs/advanced_setup.md +7 -64
  11. data/docs/command_line_interface.md +45 -0
  12. data/docs/error_handling.md +65 -18
  13. data/docs/inspecting_the_queue.md +30 -80
  14. data/docs/job_helper_methods.md +27 -0
  15. data/docs/logging.md +3 -22
  16. data/docs/managing_workers.md +6 -61
  17. data/docs/middleware.md +15 -0
  18. data/docs/migrating.md +4 -7
  19. data/docs/multiple_queues.md +8 -4
  20. data/docs/shutting_down_safely.md +1 -1
  21. data/docs/using_plain_connections.md +39 -15
  22. data/docs/using_sequel.md +5 -3
  23. data/docs/writing_reliable_jobs.md +15 -24
  24. data/lib/que.rb +98 -182
  25. data/lib/que/active_job/extensions.rb +97 -0
  26. data/lib/que/active_record/connection.rb +51 -0
  27. data/lib/que/active_record/model.rb +48 -0
  28. data/lib/que/connection.rb +179 -0
  29. data/lib/que/connection_pool.rb +78 -0
  30. data/lib/que/job.rb +107 -156
  31. data/lib/que/job_cache.rb +240 -0
  32. data/lib/que/job_methods.rb +168 -0
  33. data/lib/que/listener.rb +176 -0
  34. data/lib/que/locker.rb +466 -0
  35. data/lib/que/metajob.rb +47 -0
  36. data/lib/que/migrations.rb +24 -17
  37. data/lib/que/migrations/4/down.sql +48 -0
  38. data/lib/que/migrations/4/up.sql +265 -0
  39. data/lib/que/poller.rb +267 -0
  40. data/lib/que/rails/railtie.rb +14 -0
  41. data/lib/que/result_queue.rb +35 -0
  42. data/lib/que/sequel/model.rb +51 -0
  43. data/lib/que/utils/assertions.rb +62 -0
  44. data/lib/que/utils/constantization.rb +19 -0
  45. data/lib/que/utils/error_notification.rb +68 -0
  46. data/lib/que/utils/freeze.rb +20 -0
  47. data/lib/que/utils/introspection.rb +50 -0
  48. data/lib/que/utils/json_serialization.rb +21 -0
  49. data/lib/que/utils/logging.rb +78 -0
  50. data/lib/que/utils/middleware.rb +33 -0
  51. data/lib/que/utils/queue_management.rb +18 -0
  52. data/lib/que/utils/transactions.rb +34 -0
  53. data/lib/que/version.rb +1 -1
  54. data/lib/que/worker.rb +128 -167
  55. data/que.gemspec +13 -2
  56. metadata +37 -80
  57. data/.rspec +0 -2
  58. data/.travis.yml +0 -64
  59. data/Gemfile +0 -24
  60. data/docs/customizing_que.md +0 -200
  61. data/lib/generators/que/install_generator.rb +0 -24
  62. data/lib/generators/que/templates/add_que.rb +0 -13
  63. data/lib/que/adapters/active_record.rb +0 -40
  64. data/lib/que/adapters/base.rb +0 -133
  65. data/lib/que/adapters/connection_pool.rb +0 -16
  66. data/lib/que/adapters/pg.rb +0 -21
  67. data/lib/que/adapters/pond.rb +0 -16
  68. data/lib/que/adapters/sequel.rb +0 -20
  69. data/lib/que/railtie.rb +0 -16
  70. data/lib/que/rake_tasks.rb +0 -59
  71. data/lib/que/sql.rb +0 -170
  72. data/spec/adapters/active_record_spec.rb +0 -175
  73. data/spec/adapters/connection_pool_spec.rb +0 -22
  74. data/spec/adapters/pg_spec.rb +0 -41
  75. data/spec/adapters/pond_spec.rb +0 -22
  76. data/spec/adapters/sequel_spec.rb +0 -57
  77. data/spec/gemfiles/Gemfile.current +0 -19
  78. data/spec/gemfiles/Gemfile.old +0 -19
  79. data/spec/gemfiles/Gemfile.older +0 -19
  80. data/spec/gemfiles/Gemfile.oldest +0 -19
  81. data/spec/spec_helper.rb +0 -129
  82. data/spec/support/helpers.rb +0 -25
  83. data/spec/support/jobs.rb +0 -35
  84. data/spec/support/shared_examples/adapter.rb +0 -42
  85. data/spec/support/shared_examples/multi_threaded_adapter.rb +0 -46
  86. data/spec/unit/configuration_spec.rb +0 -31
  87. data/spec/unit/connection_spec.rb +0 -14
  88. data/spec/unit/customization_spec.rb +0 -251
  89. data/spec/unit/enqueue_spec.rb +0 -245
  90. data/spec/unit/helper_spec.rb +0 -12
  91. data/spec/unit/logging_spec.rb +0 -101
  92. data/spec/unit/migrations_spec.rb +0 -84
  93. data/spec/unit/pool_spec.rb +0 -365
  94. data/spec/unit/run_spec.rb +0 -14
  95. data/spec/unit/states_spec.rb +0 -50
  96. data/spec/unit/stats_spec.rb +0 -46
  97. data/spec/unit/transaction_spec.rb +0 -36
  98. data/spec/unit/work_spec.rb +0 -596
  99. data/spec/unit/worker_spec.rb +0 -167
  100. data/tasks/benchmark.rb +0 -3
  101. data/tasks/rspec.rb +0 -14
  102. data/tasks/safe_shutdown.rb +0 -67
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- describe Que::Job, '.run' do
6
- it "should immediately process the job with the arguments given to it" do
7
- result = ArgsJob.run 1, 'two', {:three => 3}
8
- result.should be_an_instance_of ArgsJob
9
- result.attrs[:args].should == [1, 'two', {:three => 3}]
10
-
11
- DB[:que_jobs].count.should be 0
12
- $passed_args.should == [1, 'two', {:three => 3}]
13
- end
14
- end
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- describe Que, '.worker_states' do
6
- it "should return a list of the job types in the queue, their counts and the number of each currently running" do
7
- Que.adapter = QUE_ADAPTERS[:connection_pool]
8
-
9
- class WorkerStateJob < BlockJob
10
- def run
11
- $pid = Que.execute("select pg_backend_pid()").first[:pg_backend_pid]
12
- super
13
- end
14
- end
15
-
16
- WorkerStateJob.enqueue :priority => 2
17
-
18
- # Ensure that the portion of the SQL query that accounts for bigint
19
- # job_ids functions correctly.
20
- DB[:que_jobs].update(:job_id => 2**33)
21
-
22
- t = Thread.new { Que::Job.work }
23
- $q1.pop
24
-
25
- states = Que.worker_states
26
- states.length.should be 1
27
-
28
- $q2.push nil
29
- t.join
30
-
31
- state = states.first
32
- state.keys.should == %w(priority run_at job_id job_class args error_count last_error queue pg_backend_pid pg_state pg_state_changed_at pg_last_query pg_last_query_started_at pg_transaction_started_at pg_waiting_on_lock)
33
-
34
- state[:priority].should == 2
35
- state[:run_at].should be_within(3).of Time.now
36
- state[:job_id].should == 2**33
37
- state[:job_class].should == 'WorkerStateJob'
38
- state[:args].should == []
39
- state[:error_count].should == 0
40
- state[:last_error].should be nil
41
-
42
- state[:pg_backend_pid].should == $pid
43
- state[:pg_state].should == 'idle'
44
- state[:pg_state_changed_at].should be_within(3).of Time.now
45
- state[:pg_last_query].should == 'select pg_backend_pid()'
46
- state[:pg_last_query_started_at].should be_within(3).of Time.now
47
- state[:pg_transaction_started_at].should == nil
48
- state[:pg_waiting_on_lock].should == false
49
- end if QUE_ADAPTERS[:connection_pool]
50
- end
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- describe Que, '.job_stats' do
6
- it "should return a list of the job types in the queue, their counts and the number of each currently running" do
7
- BlockJob.enqueue
8
- Que::Job.enqueue
9
-
10
- # Have to tweak the job_id to ensure that the portion of the SQL query
11
- # that accounts for bigint job_ids functions correctly.
12
- old = Time.now - 3600
13
- DB[:que_jobs].where(:job_class => "Que::Job").update(:job_id => 2**33, :error_count => 5, :run_at => old)
14
-
15
- Que::Job.enqueue
16
-
17
- begin
18
- DB.get{pg_advisory_lock(2**33)}
19
-
20
- stats = Que.job_stats
21
- stats.length.should == 2
22
-
23
- qj, bj = stats
24
-
25
- qj.keys.should == %w(queue job_class count count_working count_errored highest_error_count oldest_run_at)
26
-
27
- qj[:queue].should == ''
28
- qj[:job_class].should == 'Que::Job'
29
- qj[:count].should == 2
30
- qj[:count_working].should == 1
31
- qj[:count_errored].should == 1
32
- qj[:highest_error_count].should == 5
33
- qj[:oldest_run_at].should be_within(3).of old
34
-
35
- bj[:queue].should == ''
36
- bj[:job_class].should == 'BlockJob'
37
- bj[:count].should == 1
38
- bj[:count_working].should == 0
39
- bj[:count_errored].should == 0
40
- bj[:highest_error_count].should == 0
41
- bj[:oldest_run_at].should be_within(3).of Time.now
42
- ensure
43
- DB.get{pg_advisory_unlock_all.function}
44
- end
45
- end
46
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- describe Que, '.transaction' do
6
- it "should use a transaction to rollback changes in the event of an error" do
7
- proc do
8
- Que.transaction do
9
- Que.execute "DROP TABLE que_jobs"
10
- Que.execute "invalid SQL syntax"
11
- end
12
- end.should raise_error(PG::Error)
13
-
14
- DB.table_exists?(:que_jobs).should be true
15
- end
16
-
17
- unless RUBY_VERSION.start_with?('1.9')
18
- it "should rollback correctly in the event of a killed thread" do
19
- q = Queue.new
20
-
21
- t = Thread.new do
22
- Que.transaction do
23
- Que.execute "DROP TABLE que_jobs"
24
- q.push :go!
25
- sleep
26
- end
27
- end
28
-
29
- q.pop
30
- t.kill
31
- t.join
32
-
33
- DB.table_exists?(:que_jobs).should be true
34
- end
35
- end
36
- end
@@ -1,596 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- describe Que::Job, '.work' do
6
- it "should pass a job's arguments to the run method and delete it from the database" do
7
- ArgsJob.enqueue 1, 'two', {'three' => 3}
8
- DB[:que_jobs].count.should be 1
9
-
10
- result = Que::Job.work
11
- result[:event].should == :job_worked
12
- result[:job][:job_class].should == 'ArgsJob'
13
-
14
- DB[:que_jobs].count.should be 0
15
- $passed_args.should == [1, 'two', {'three' => 3}]
16
- end
17
-
18
- it "should respect a custom json converter when processing the job's arguments" do
19
- ArgsJob.enqueue 1, 'two', {'three' => 3}
20
- DB[:que_jobs].count.should be 1
21
-
22
- begin
23
- Que.json_converter = Que::SYMBOLIZER
24
-
25
- result = Que::Job.work
26
- result[:event].should == :job_worked
27
- result[:job][:job_class].should == 'ArgsJob'
28
-
29
- DB[:que_jobs].count.should be 0
30
- $passed_args.should == [1, 'two', {:three => 3}]
31
- ensure
32
- Que.json_converter = Que::INDIFFERENTIATOR
33
- end
34
- end
35
-
36
- it "should default to only working jobs without a named queue" do
37
- Que::Job.enqueue 1, :queue => 'other_queue'
38
- Que::Job.enqueue 2
39
-
40
- result = Que::Job.work
41
- result[:event].should == :job_worked
42
- result[:job][:args].should == [2]
43
-
44
- result = Que::Job.work
45
- result[:event].should == :job_unavailable
46
- end
47
-
48
- it "should accept the name of a single queue to pull jobs from" do
49
- Que::Job.enqueue 1, :queue => 'other_queue'
50
- Que::Job.enqueue 2, :queue => 'other_queue'
51
- Que::Job.enqueue 3
52
-
53
- result = Que::Job.work(:other_queue)
54
- result[:event].should == :job_worked
55
- result[:job][:args].should == [1]
56
-
57
- result = Que::Job.work('other_queue')
58
- result[:event].should == :job_worked
59
- result[:job][:args].should == [2]
60
-
61
- result = Que::Job.work(:other_queue)
62
- result[:event].should == :job_unavailable
63
- end
64
-
65
- it "should make a job's argument hashes indifferently accessible" do
66
- DB[:que_jobs].count.should be 0
67
- ArgsJob.enqueue 1, 'two', {'array' => [{'number' => 3}]}
68
- DB[:que_jobs].count.should be 1
69
-
70
- result = Que::Job.work
71
- result[:event].should == :job_worked
72
- result[:job][:job_class].should == 'ArgsJob'
73
-
74
- DB[:que_jobs].count.should be 0
75
-
76
- $passed_args.last[:array].first[:number].should == 3
77
- end
78
-
79
- it "should not fail if there are no jobs to work" do
80
- Que::Job.work[:event].should be :job_unavailable
81
- end
82
-
83
- it "should prefer a job with a higher priority" do
84
- # 1 is highest priority.
85
- [5, 4, 3, 2, 1, 2, 3, 4, 5].map{|p| Que::Job.enqueue :priority => p}
86
- DB[:que_jobs].order(:job_id).select_map(:priority).should == [5, 4, 3, 2, 1, 2, 3, 4, 5]
87
-
88
- result = Que::Job.work
89
- result[:event].should == :job_worked
90
- result[:job][:job_class].should == 'Que::Job'
91
- DB[:que_jobs].select_map(:priority).should == [5, 4, 3, 2, 2, 3, 4, 5]
92
- end
93
-
94
- it "should prefer a job that was scheduled to run longer ago when priorities are equal" do
95
- Que::Job.enqueue :run_at => Time.now - 30
96
- Que::Job.enqueue :run_at => Time.now - 60
97
- Que::Job.enqueue :run_at => Time.now - 30
98
-
99
- recent1, old, recent2 = DB[:que_jobs].order(:job_id).select_map(:run_at)
100
-
101
- result = Que::Job.work
102
- result[:event].should == :job_worked
103
- result[:job][:job_class].should == 'Que::Job'
104
- DB[:que_jobs].order_by(:job_id).select_map(:run_at).should == [recent1, recent2]
105
- end
106
-
107
- it "should prefer a job that was queued earlier when priorities and run_ats are equal" do
108
- run_at = Time.now - 30
109
- Que::Job.enqueue :run_at => run_at
110
- Que::Job.enqueue :run_at => run_at
111
- Que::Job.enqueue :run_at => run_at
112
-
113
- first, second, third = DB[:que_jobs].select_order_map(:job_id)
114
-
115
- result = Que::Job.work
116
- result[:event].should == :job_worked
117
- result[:job][:job_class].should == 'Que::Job'
118
- DB[:que_jobs].select_order_map(:job_id).should == [second, third]
119
- end
120
-
121
- it "should only work a job whose scheduled time to run has passed" do
122
- Que::Job.enqueue :run_at => Time.now + 30
123
- Que::Job.enqueue :run_at => Time.now - 30
124
- Que::Job.enqueue :run_at => Time.now + 30
125
-
126
- future1, past, future2 = DB[:que_jobs].order(:job_id).select_map(:run_at)
127
-
128
- result = Que::Job.work
129
- result[:event].should == :job_worked
130
- result[:job][:job_class].should == 'Que::Job'
131
- Que::Job.work[:event].should be :job_unavailable
132
- DB[:que_jobs].order_by(:job_id).select_map(:run_at).should == [future1, future2]
133
- end
134
-
135
- it "should lock the job it selects" do
136
- BlockJob.enqueue
137
- id = DB[:que_jobs].get(:job_id)
138
- thread = Thread.new { Que::Job.work }
139
-
140
- $q1.pop
141
- DB[:pg_locks].where(:locktype => 'advisory').select_map(:objid).should == [id]
142
- $q2.push nil
143
-
144
- thread.join
145
- end
146
-
147
- it "should skip jobs that are advisory-locked" do
148
- Que::Job.enqueue :priority => 2
149
- Que::Job.enqueue :priority => 1
150
- Que::Job.enqueue :priority => 3
151
- id = DB[:que_jobs].where(:priority => 1).get(:job_id)
152
-
153
- begin
154
- DB.select{pg_advisory_lock(id)}.single_value
155
-
156
- result = Que::Job.work
157
- result[:event].should == :job_worked
158
- result[:job][:job_class].should == 'Que::Job'
159
-
160
- DB[:que_jobs].order_by(:job_id).select_map(:priority).should == [1, 3]
161
- ensure
162
- DB.select{pg_advisory_unlock(id)}.single_value
163
- end
164
- end
165
-
166
- it "should handle subclasses of other jobs" do
167
- class SubClassJob < Que::Job
168
- @priority = 2
169
-
170
- def run
171
- $job_spec_result << :sub
172
- end
173
- end
174
-
175
- class SubSubClassJob < SubClassJob
176
- @priority = 4
177
-
178
- def run
179
- super
180
- $job_spec_result << :subsub
181
- end
182
- end
183
-
184
- $job_spec_result = []
185
- SubClassJob.enqueue
186
- DB[:que_jobs].select_map(:priority).should == [2]
187
- result = Que::Job.work
188
- result[:event].should == :job_worked
189
- result[:job][:job_class].should == 'SubClassJob'
190
- $job_spec_result.should == [:sub]
191
-
192
- $job_spec_result = []
193
- SubSubClassJob.enqueue
194
- DB[:que_jobs].select_map(:priority).should == [4]
195
- result = Que::Job.work
196
- result[:event].should == :job_worked
197
- result[:job][:job_class].should == 'SubSubClassJob'
198
- $job_spec_result.should == [:sub, :subsub]
199
- end
200
-
201
- it "should handle namespaced subclasses" do
202
- module ModuleJobModule
203
- class ModuleJob < Que::Job
204
- end
205
- end
206
-
207
- ModuleJobModule::ModuleJob.enqueue
208
- DB[:que_jobs].get(:job_class).should == "ModuleJobModule::ModuleJob"
209
-
210
- result = Que::Job.work
211
- result[:event].should == :job_worked
212
- result[:job][:job_class].should == 'ModuleJobModule::ModuleJob'
213
- end
214
-
215
- it "should make it easy to destroy the job within the same transaction as other changes" do
216
- class DestroyJob < Que::Job
217
- def run
218
- destroy
219
- end
220
- end
221
-
222
- DestroyJob.enqueue
223
- DB[:que_jobs].count.should be 1
224
- Que::Job.work
225
- DB[:que_jobs].count.should be 0
226
- end
227
-
228
- describe "when encountering an error" do
229
- it "should exponentially back off the job" do
230
- ErrorJob.enqueue
231
-
232
- result = Que::Job.work
233
- result[:event].should == :job_errored
234
- result[:error].should be_an_instance_of RuntimeError
235
- result[:job][:job_class].should == 'ErrorJob'
236
-
237
- DB[:que_jobs].count.should be 1
238
- job = DB[:que_jobs].first
239
- job[:error_count].should be 1
240
- job[:last_error].should =~ /\ARuntimeError: ErrorJob!/
241
- job[:run_at].should be_within(3).of Time.now + 4
242
-
243
- DB[:que_jobs].update :error_count => 5,
244
- :run_at => Time.now - 60
245
-
246
- result = Que::Job.work
247
- result[:event].should == :job_errored
248
- result[:error].should be_an_instance_of RuntimeError
249
- result[:job][:job_class].should == 'ErrorJob'
250
-
251
- DB[:que_jobs].count.should be 1
252
- job = DB[:que_jobs].first
253
- job[:error_count].should be 6
254
- job[:last_error].should =~ /\ARuntimeError: ErrorJob!/
255
- job[:run_at].should be_within(3).of Time.now + 1299
256
- end
257
-
258
- it "should respect a custom retry interval" do
259
- class RetryIntervalJob < ErrorJob
260
- @retry_interval = 3155760000000 # 100,000 years from now
261
- end
262
-
263
- RetryIntervalJob.enqueue
264
-
265
- result = Que::Job.work
266
- result[:event].should == :job_errored
267
- result[:error].should be_an_instance_of RuntimeError
268
- result[:job][:job_class].should == 'RetryIntervalJob'
269
-
270
- DB[:que_jobs].count.should be 1
271
- job = DB[:que_jobs].first
272
- job[:error_count].should be 1
273
- job[:last_error].should =~ /\ARuntimeError: ErrorJob!/
274
- job[:run_at].to_f.should be_within(3).of Time.now.to_f + RetryIntervalJob.retry_interval
275
-
276
- DB[:que_jobs].update :error_count => 5,
277
- :run_at => Time.now - 60
278
-
279
- result = Que::Job.work
280
- result[:event].should == :job_errored
281
- result[:error].should be_an_instance_of RuntimeError
282
- result[:job][:job_class].should == 'RetryIntervalJob'
283
-
284
- DB[:que_jobs].count.should be 1
285
- job = DB[:que_jobs].first
286
- job[:error_count].should be 6
287
- job[:last_error].should =~ /\ARuntimeError: ErrorJob!/
288
- job[:run_at].to_f.should be_within(3).of Time.now.to_f + RetryIntervalJob.retry_interval
289
- end
290
-
291
- it "should respect a custom retry interval formula" do
292
- class RetryIntervalFormulaJob < ErrorJob
293
- @retry_interval = proc { |count| count * 10 }
294
- end
295
-
296
- RetryIntervalFormulaJob.enqueue
297
-
298
- result = Que::Job.work
299
- result[:event].should == :job_errored
300
- result[:error].should be_an_instance_of RuntimeError
301
- result[:job][:job_class].should == 'RetryIntervalFormulaJob'
302
-
303
- DB[:que_jobs].count.should be 1
304
- job = DB[:que_jobs].first
305
- job[:error_count].should be 1
306
- job[:last_error].should =~ /\ARuntimeError: ErrorJob!/
307
- job[:run_at].should be_within(3).of Time.now + 10
308
-
309
- DB[:que_jobs].update :error_count => 5,
310
- :run_at => Time.now - 60
311
-
312
- result = Que::Job.work
313
- result[:event].should == :job_errored
314
- result[:error].should be_an_instance_of RuntimeError
315
- result[:job][:job_class].should == 'RetryIntervalFormulaJob'
316
-
317
- DB[:que_jobs].count.should be 1
318
- job = DB[:que_jobs].first
319
- job[:error_count].should be 6
320
- job[:last_error].should =~ /\ARuntimeError: ErrorJob!/
321
- job[:run_at].should be_within(3).of Time.now + 60
322
- end
323
-
324
- it "should pass it to an error notifier, if one is defined" do
325
- begin
326
- errors = []
327
- Que.error_notifier = proc { |error| errors << error }
328
-
329
- ErrorJob.enqueue
330
-
331
- result = Que::Job.work
332
- result[:event].should == :job_errored
333
- result[:error].should be_an_instance_of RuntimeError
334
- result[:job][:job_class].should == 'ErrorJob'
335
-
336
- errors.count.should be 1
337
- error = errors[0]
338
- error.should be_an_instance_of RuntimeError
339
- error.message.should == "ErrorJob!"
340
- ensure
341
- Que.error_notifier = nil
342
- end
343
- end
344
-
345
- it "should pass job to an error notifier, if one is defined" do
346
- begin
347
- jobs = []
348
- Que.error_notifier = proc { |error, job| jobs << job }
349
-
350
- ErrorJob.enqueue
351
- result = Que::Job.work
352
-
353
- jobs.count.should be 1
354
- job = jobs[0]
355
- job.should be result[:job]
356
- ensure
357
- Que.error_notifier = nil
358
- end
359
- end
360
-
361
- it "should not do anything if the error notifier itelf throws an error" do
362
- begin
363
- Que.error_notifier = proc { |error| raise "Another error!" }
364
- ErrorJob.enqueue
365
-
366
- result = Que::Job.work
367
- result[:event].should == :job_errored
368
- result[:error].should be_an_instance_of RuntimeError
369
- ensure
370
- Que.error_notifier = nil
371
- end
372
- end
373
-
374
- it "should throw an error properly if there's no corresponding job class" do
375
- DB[:que_jobs].insert :job_class => "NonexistentClass"
376
-
377
- result = Que::Job.work
378
- result[:event].should == :job_errored
379
- result[:error].should be_an_instance_of NameError
380
- result[:job][:job_class].should == 'NonexistentClass'
381
-
382
- DB[:que_jobs].count.should be 1
383
- job = DB[:que_jobs].first
384
- job[:error_count].should be 1
385
- job[:last_error].should =~ /uninitialized constant:? NonexistentClass/
386
- job[:run_at].should be_within(3).of Time.now + 4
387
- end
388
-
389
- it "should throw an error properly if the corresponding job class doesn't descend from Que::Job" do
390
- class J
391
- def run(*args)
392
- end
393
- end
394
-
395
- Que.enqueue :job_class => "J"
396
-
397
- result = Que::Job.work
398
- result[:event].should == :job_errored
399
- result[:job][:job_class].should == 'J'
400
-
401
- DB[:que_jobs].count.should be 1
402
- job = DB[:que_jobs].first
403
- job[:error_count].should be 1
404
- job[:run_at].should be_within(3).of Time.now + 4
405
- end
406
-
407
- it "should use the class name of the exception if its message is blank when setting last_error" do
408
- class BlankExceptionMessageJob < Que::Job
409
- def self.error
410
- @error ||= RuntimeError.new("")
411
- end
412
-
413
- def run
414
- raise self.class.error
415
- end
416
- end
417
-
418
- BlankExceptionMessageJob.enqueue
419
- result = Que::Job.work
420
- result[:event].should == :job_errored
421
- job = DB[:que_jobs].first
422
- job[:error_count].should be 1
423
- last_error_lines = job[:last_error].split("\n")
424
- last_error_lines.should == %w[RuntimeError] + BlankExceptionMessageJob.error.backtrace
425
- end
426
-
427
- it "should use the class name of the exception if its message is blank when setting last_error" do
428
- class LongExceptionMessageJob < Que::Job
429
- def self.error
430
- @error ||= RuntimeError.new("a" * 500)
431
- end
432
-
433
- def run
434
- raise self.class.error
435
- end
436
- end
437
-
438
- LongExceptionMessageJob.enqueue
439
- result = Que::Job.work
440
- result[:event].should == :job_errored
441
- job = DB[:que_jobs].first
442
- job[:error_count].should be 1
443
- last_error_lines = job[:last_error].split("\n")
444
- last_error_lines.should == ["RuntimeError: #{'a' * 486}"] + LongExceptionMessageJob.error.backtrace
445
- end
446
-
447
- context "in a job class that has a custom error handler" do
448
- it "should allow it to schedule a retry after a specific interval" do
449
- begin
450
- error = nil
451
- Que.error_notifier = proc { |e| error = e }
452
-
453
- class CustomRetryIntervalJob < Que::Job
454
- def run(*args)
455
- raise "Blah!"
456
- end
457
-
458
- private
459
-
460
- def handle_error(error)
461
- retry_in(42)
462
- end
463
- end
464
-
465
- CustomRetryIntervalJob.enqueue
466
-
467
- result = Que::Job.work
468
- result[:event].should == :job_errored
469
- result[:error].should be_an_instance_of RuntimeError
470
- result[:job][:job_class].should == 'CustomRetryIntervalJob'
471
-
472
- DB[:que_jobs].count.should be 1
473
- job = DB[:que_jobs].first
474
- job[:error_count].should be 1
475
-
476
- lines = job[:last_error].split("\n")
477
- lines[0].should == "RuntimeError: Blah!"
478
- lines[1].should =~ /work_spec/
479
- job[:run_at].should be_within(3).of Time.now + 42
480
-
481
- error.should == result[:error]
482
- ensure
483
- Que.error_notifier = nil
484
- end
485
- end
486
-
487
- it "should allow it to destroy the job" do
488
- begin
489
- error = nil
490
- Que.error_notifier = proc { |e| error = e }
491
-
492
- class CustomRetryIntervalJob < Que::Job
493
- def run(*args)
494
- raise "Blah!"
495
- end
496
-
497
- private
498
-
499
- def handle_error(error)
500
- destroy
501
- end
502
- end
503
-
504
- CustomRetryIntervalJob.enqueue
505
-
506
- result = Que::Job.work
507
- result[:event].should == :job_errored
508
- result[:error].should be_an_instance_of RuntimeError
509
- result[:job][:job_class].should == 'CustomRetryIntervalJob'
510
-
511
- DB[:que_jobs].count.should be 0
512
-
513
- error.should == result[:error]
514
- ensure
515
- Que.error_notifier = nil
516
- end
517
- end
518
-
519
- it "should allow it to return false to skip the error notification" do
520
- begin
521
- error = nil
522
- Que.error_notifier = proc { |e| error = e }
523
-
524
- class CustomRetryIntervalJob < Que::Job
525
- def run(*args)
526
- raise "Blah!"
527
- end
528
-
529
- private
530
-
531
- def handle_error(error)
532
- false
533
- end
534
- end
535
-
536
- CustomRetryIntervalJob.enqueue
537
-
538
- result = Que::Job.work
539
- result[:event].should == :job_errored
540
- result[:error].should be_an_instance_of RuntimeError
541
- result[:job][:job_class].should == 'CustomRetryIntervalJob'
542
-
543
- DB[:que_jobs].count.should be 0
544
-
545
- error.should == nil
546
- ensure
547
- Que.error_notifier = nil
548
- end
549
- end
550
-
551
- it "should allow it to call super to get the default behavior" do
552
- begin
553
- error = nil
554
- Que.error_notifier = proc { |e| error = e }
555
-
556
- class CustomRetryIntervalJob < Que::Job
557
- def run(*args)
558
- raise "Blah!"
559
- end
560
-
561
- private
562
-
563
- def handle_error(error)
564
- case error
565
- when RuntimeError
566
- super
567
- else
568
- $error_handler_failed = true
569
- raise "Bad!"
570
- end
571
- end
572
- end
573
-
574
- CustomRetryIntervalJob.enqueue
575
-
576
- result = Que::Job.work
577
- result[:event].should == :job_errored
578
- result[:error].should be_an_instance_of RuntimeError
579
- result[:job][:job_class].should == 'CustomRetryIntervalJob'
580
-
581
- $error_handler_failed.should == nil
582
-
583
- DB[:que_jobs].count.should be 1
584
- job = DB[:que_jobs].first
585
- job[:error_count].should be 1
586
- job[:last_error].should =~ /\ARuntimeError: Blah!/
587
- job[:run_at].should be_within(3).of Time.now + 4
588
-
589
- error.should == result[:error]
590
- ensure
591
- Que.error_notifier = nil
592
- end
593
- end
594
- end
595
- end
596
- end