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.
- checksums.yaml +5 -5
- data/.gitignore +2 -0
- data/CHANGELOG.md +108 -14
- data/LICENSE.txt +1 -1
- data/README.md +49 -45
- data/bin/command_line_interface.rb +239 -0
- data/bin/que +8 -82
- data/docs/README.md +2 -0
- data/docs/active_job.md +6 -0
- data/docs/advanced_setup.md +7 -64
- data/docs/command_line_interface.md +45 -0
- data/docs/error_handling.md +65 -18
- data/docs/inspecting_the_queue.md +30 -80
- data/docs/job_helper_methods.md +27 -0
- data/docs/logging.md +3 -22
- data/docs/managing_workers.md +6 -61
- data/docs/middleware.md +15 -0
- data/docs/migrating.md +4 -7
- data/docs/multiple_queues.md +8 -4
- data/docs/shutting_down_safely.md +1 -1
- data/docs/using_plain_connections.md +39 -15
- data/docs/using_sequel.md +5 -3
- data/docs/writing_reliable_jobs.md +15 -24
- data/lib/que.rb +98 -182
- data/lib/que/active_job/extensions.rb +97 -0
- data/lib/que/active_record/connection.rb +51 -0
- data/lib/que/active_record/model.rb +48 -0
- data/lib/que/connection.rb +179 -0
- data/lib/que/connection_pool.rb +78 -0
- data/lib/que/job.rb +107 -156
- data/lib/que/job_cache.rb +240 -0
- data/lib/que/job_methods.rb +168 -0
- data/lib/que/listener.rb +176 -0
- data/lib/que/locker.rb +466 -0
- data/lib/que/metajob.rb +47 -0
- data/lib/que/migrations.rb +24 -17
- data/lib/que/migrations/4/down.sql +48 -0
- data/lib/que/migrations/4/up.sql +265 -0
- data/lib/que/poller.rb +267 -0
- data/lib/que/rails/railtie.rb +14 -0
- data/lib/que/result_queue.rb +35 -0
- data/lib/que/sequel/model.rb +51 -0
- data/lib/que/utils/assertions.rb +62 -0
- data/lib/que/utils/constantization.rb +19 -0
- data/lib/que/utils/error_notification.rb +68 -0
- data/lib/que/utils/freeze.rb +20 -0
- data/lib/que/utils/introspection.rb +50 -0
- data/lib/que/utils/json_serialization.rb +21 -0
- data/lib/que/utils/logging.rb +78 -0
- data/lib/que/utils/middleware.rb +33 -0
- data/lib/que/utils/queue_management.rb +18 -0
- data/lib/que/utils/transactions.rb +34 -0
- data/lib/que/version.rb +1 -1
- data/lib/que/worker.rb +128 -167
- data/que.gemspec +13 -2
- metadata +37 -80
- data/.rspec +0 -2
- data/.travis.yml +0 -64
- data/Gemfile +0 -24
- data/docs/customizing_que.md +0 -200
- data/lib/generators/que/install_generator.rb +0 -24
- data/lib/generators/que/templates/add_que.rb +0 -13
- data/lib/que/adapters/active_record.rb +0 -40
- data/lib/que/adapters/base.rb +0 -133
- data/lib/que/adapters/connection_pool.rb +0 -16
- data/lib/que/adapters/pg.rb +0 -21
- data/lib/que/adapters/pond.rb +0 -16
- data/lib/que/adapters/sequel.rb +0 -20
- data/lib/que/railtie.rb +0 -16
- data/lib/que/rake_tasks.rb +0 -59
- data/lib/que/sql.rb +0 -170
- data/spec/adapters/active_record_spec.rb +0 -175
- data/spec/adapters/connection_pool_spec.rb +0 -22
- data/spec/adapters/pg_spec.rb +0 -41
- data/spec/adapters/pond_spec.rb +0 -22
- data/spec/adapters/sequel_spec.rb +0 -57
- data/spec/gemfiles/Gemfile.current +0 -19
- data/spec/gemfiles/Gemfile.old +0 -19
- data/spec/gemfiles/Gemfile.older +0 -19
- data/spec/gemfiles/Gemfile.oldest +0 -19
- data/spec/spec_helper.rb +0 -129
- data/spec/support/helpers.rb +0 -25
- data/spec/support/jobs.rb +0 -35
- data/spec/support/shared_examples/adapter.rb +0 -42
- data/spec/support/shared_examples/multi_threaded_adapter.rb +0 -46
- data/spec/unit/configuration_spec.rb +0 -31
- data/spec/unit/connection_spec.rb +0 -14
- data/spec/unit/customization_spec.rb +0 -251
- data/spec/unit/enqueue_spec.rb +0 -245
- data/spec/unit/helper_spec.rb +0 -12
- data/spec/unit/logging_spec.rb +0 -101
- data/spec/unit/migrations_spec.rb +0 -84
- data/spec/unit/pool_spec.rb +0 -365
- data/spec/unit/run_spec.rb +0 -14
- data/spec/unit/states_spec.rb +0 -50
- data/spec/unit/stats_spec.rb +0 -46
- data/spec/unit/transaction_spec.rb +0 -36
- data/spec/unit/work_spec.rb +0 -596
- data/spec/unit/worker_spec.rb +0 -167
- data/tasks/benchmark.rb +0 -3
- data/tasks/rspec.rb +0 -14
- data/tasks/safe_shutdown.rb +0 -67
data/spec/unit/run_spec.rb
DELETED
@@ -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
|
data/spec/unit/states_spec.rb
DELETED
@@ -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
|
data/spec/unit/stats_spec.rb
DELETED
@@ -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
|
data/spec/unit/work_spec.rb
DELETED
@@ -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
|