que 0.14.3 → 1.0.0.beta

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 (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