inst-jobs 3.1.2 → 3.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/delayed/lifecycle.rb +7 -1
- data/lib/delayed/version.rb +1 -1
- metadata +3 -51
- data/spec/active_record_job_spec.rb +0 -326
- data/spec/delayed/cli_spec.rb +0 -25
- data/spec/delayed/daemon_spec.rb +0 -38
- data/spec/delayed/message_sending_spec.rb +0 -108
- data/spec/delayed/periodic_spec.rb +0 -32
- data/spec/delayed/server_spec.rb +0 -103
- data/spec/delayed/settings_spec.rb +0 -48
- data/spec/delayed/work_queue/in_process_spec.rb +0 -31
- data/spec/delayed/work_queue/parent_process/client_spec.rb +0 -87
- data/spec/delayed/work_queue/parent_process/server_spec.rb +0 -280
- data/spec/delayed/work_queue/parent_process_spec.rb +0 -60
- data/spec/delayed/worker/consul_health_check_spec.rb +0 -63
- data/spec/delayed/worker/health_check_spec.rb +0 -134
- data/spec/delayed/worker_spec.rb +0 -106
- data/spec/migrate/20140924140513_add_story_table.rb +0 -9
- data/spec/sample_jobs.rb +0 -79
- data/spec/shared/delayed_batch.rb +0 -105
- data/spec/shared/delayed_method.rb +0 -287
- data/spec/shared/performable_method.rb +0 -75
- data/spec/shared/shared_backend.rb +0 -1238
- data/spec/shared/testing.rb +0 -50
- data/spec/shared/worker.rb +0 -413
- data/spec/shared_jobs_specs.rb +0 -17
- data/spec/spec_helper.rb +0 -138
@@ -1,134 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "spec_helper"
|
4
|
-
|
5
|
-
RSpec.describe Delayed::Worker::HealthCheck do
|
6
|
-
let(:klass) { Class.new(Delayed::Worker::HealthCheck) { self.type_name = :test } }
|
7
|
-
|
8
|
-
before do
|
9
|
-
klass # Gotta make sure the class has been defined before we try to use it
|
10
|
-
end
|
11
|
-
|
12
|
-
after do
|
13
|
-
described_class.subclasses.delete(klass)
|
14
|
-
end
|
15
|
-
|
16
|
-
it "must maintain a list of its subclasses" do
|
17
|
-
klass
|
18
|
-
expect(described_class.subclasses).to include klass
|
19
|
-
end
|
20
|
-
|
21
|
-
describe ".build(type:, config: {})" do
|
22
|
-
it "must select the concrete class to use by the type_name in the subclass" do
|
23
|
-
check = described_class.build(type: "test", worker_name: "foobar")
|
24
|
-
expect(check).to be_a(klass)
|
25
|
-
end
|
26
|
-
|
27
|
-
it "must raise ArgumentError when the specified type doesn't exist" do
|
28
|
-
expect do
|
29
|
-
described_class.build(type: "nope", config: { worker_name: "foobar" })
|
30
|
-
end.to raise_error ArgumentError
|
31
|
-
end
|
32
|
-
|
33
|
-
it "must initiaize the specified class using the supplied config" do
|
34
|
-
config = { foo: "bar" }.with_indifferent_access
|
35
|
-
check = described_class.build(type: "test", worker_name: "foobar", config: config)
|
36
|
-
expect(check.config).to eq config
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
describe ".reschedule_abandoned_jobs" do
|
41
|
-
let(:klass) do
|
42
|
-
Class.new(Delayed::Worker::HealthCheck) do
|
43
|
-
self.type_name = :fake
|
44
|
-
class << self
|
45
|
-
attr_accessor :live_workers
|
46
|
-
end
|
47
|
-
|
48
|
-
def live_workers
|
49
|
-
self.class.live_workers
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
let(:initial_run_at) { 10.minutes.ago }
|
55
|
-
|
56
|
-
before do
|
57
|
-
klass.live_workers = %w[alive]
|
58
|
-
Delayed.select_backend(Delayed::Backend::ActiveRecord::Job)
|
59
|
-
|
60
|
-
2.times { Delayed::Job.enqueue(SimpleJob.new, run_at: initial_run_at, max_attempts: 4) }
|
61
|
-
@alive_job = Delayed::Job.first
|
62
|
-
@alive_job.update!({
|
63
|
-
locked_by: "alive",
|
64
|
-
locked_at: initial_run_at
|
65
|
-
})
|
66
|
-
@dead_job = Delayed::Job.last
|
67
|
-
@dead_job.update!({
|
68
|
-
locked_by: "dead",
|
69
|
-
locked_at: initial_run_at
|
70
|
-
})
|
71
|
-
Delayed::Settings.worker_health_check_type = :fake
|
72
|
-
Delayed::Settings.worker_health_check_config = {}
|
73
|
-
end
|
74
|
-
|
75
|
-
after do
|
76
|
-
described_class.subclasses.delete(klass)
|
77
|
-
Delayed::Settings.worker_health_check_type = :none
|
78
|
-
Delayed::Settings.worker_health_check_config = {}
|
79
|
-
end
|
80
|
-
|
81
|
-
it "must leave jobs locked by live workers alone" do
|
82
|
-
described_class.reschedule_abandoned_jobs
|
83
|
-
@alive_job.reload
|
84
|
-
expect(@alive_job.run_at.to_i).to eq initial_run_at.to_i
|
85
|
-
expect(@alive_job.locked_at.to_i).to eq initial_run_at.to_i
|
86
|
-
expect(@alive_job.locked_by).to eq "alive"
|
87
|
-
end
|
88
|
-
|
89
|
-
it "must reschedule jobs locked by dead workers" do
|
90
|
-
described_class.reschedule_abandoned_jobs
|
91
|
-
@dead_job.reload
|
92
|
-
expect(@dead_job.run_at).to be > initial_run_at
|
93
|
-
expect(@dead_job.locked_at).to be_nil
|
94
|
-
expect(@dead_job.locked_by).to be_nil
|
95
|
-
end
|
96
|
-
|
97
|
-
it "ignores jobs that are re-locked after fetching from db" do
|
98
|
-
Delayed::Job.where(id: @dead_job).update_all(locked_by: "someone_else")
|
99
|
-
# we need to return @dead_job itself, which doesn't match the database
|
100
|
-
jobs_scope = double
|
101
|
-
allow(jobs_scope).to receive(:where).and_return(jobs_scope)
|
102
|
-
allow(jobs_scope).to receive(:not).and_return(jobs_scope)
|
103
|
-
allow(jobs_scope).to receive(:limit).and_return(jobs_scope)
|
104
|
-
allow(jobs_scope).to receive(:to_a).and_return([@dead_job], [])
|
105
|
-
allow(Delayed::Job).to receive(:running_jobs).and_return(jobs_scope)
|
106
|
-
described_class.reschedule_abandoned_jobs
|
107
|
-
@dead_job.reload
|
108
|
-
expect(@dead_job.locked_by).to eq "someone_else"
|
109
|
-
end
|
110
|
-
|
111
|
-
it "ignores jobs that are prefetched" do
|
112
|
-
Delayed::Job.where(id: @dead_job).update_all(locked_by: "prefetch:some_node")
|
113
|
-
allow(Delayed::Job).to receive(:running_jobs).and_return(Delayed::Job.where(id: @dead_job.id))
|
114
|
-
described_class.reschedule_abandoned_jobs
|
115
|
-
@dead_job.reload
|
116
|
-
expect(@dead_job.locked_by).to eq "prefetch:some_node"
|
117
|
-
end
|
118
|
-
|
119
|
-
it "bails immediately if advisory lock already taken" do
|
120
|
-
allow(Delayed::Job).to receive(:attempt_advisory_lock).and_return(false)
|
121
|
-
described_class.reschedule_abandoned_jobs
|
122
|
-
@dead_job.reload
|
123
|
-
expect(@dead_job.run_at.to_i).to eq(initial_run_at.to_i)
|
124
|
-
expect(@dead_job.locked_at).not_to be_nil
|
125
|
-
expect(@dead_job.locked_by).not_to be_nil
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
describe "#initialize" do
|
130
|
-
it "must raise ArgumentError when the worker name is not supplied" do
|
131
|
-
expect { klass.new }.to raise_error ArgumentError
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
data/spec/delayed/worker_spec.rb
DELETED
@@ -1,106 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "../spec_helper"
|
4
|
-
|
5
|
-
describe Delayed::Worker do
|
6
|
-
subject { described_class.new(worker_config.dup) }
|
7
|
-
|
8
|
-
let(:worker_config) do
|
9
|
-
{
|
10
|
-
queue: "test", min_priority: 1, max_priority: 2, stuff: "stuff"
|
11
|
-
}.freeze
|
12
|
-
end
|
13
|
-
let(:job_attrs) do
|
14
|
-
{
|
15
|
-
id: 42, name: "testjob", full_name: "testfullname", :last_error= => nil,
|
16
|
-
attempts: 1, reschedule: nil, :expired? => false,
|
17
|
-
payload_object: {}, priority: 25
|
18
|
-
}.freeze
|
19
|
-
end
|
20
|
-
|
21
|
-
after { described_class.lifecycle.reset! }
|
22
|
-
|
23
|
-
describe "#perform" do
|
24
|
-
it "fires off an error callback when a job raises an exception" do
|
25
|
-
fired = false
|
26
|
-
described_class.lifecycle.before(:error) { |_worker, _exception| fired = true }
|
27
|
-
job = double(job_attrs)
|
28
|
-
output_count = subject.perform(job)
|
29
|
-
expect(fired).to be_truthy
|
30
|
-
expect(output_count).to eq(1)
|
31
|
-
end
|
32
|
-
|
33
|
-
it "uses the retry callback for a retriable exception" do
|
34
|
-
error_fired = retry_fired = false
|
35
|
-
described_class.lifecycle.before(:error) { |_worker, _exception| error_fired = true }
|
36
|
-
described_class.lifecycle.before(:retry) { |_worker, _exception| retry_fired = true }
|
37
|
-
job = Delayed::Job.new(payload_object: {}, priority: 25, strand: "test_jobs", max_attempts: 3)
|
38
|
-
expect(job).to receive(:invoke_job) do
|
39
|
-
raise Delayed::RetriableError, "that's all this job does"
|
40
|
-
end
|
41
|
-
output_count = subject.perform(job)
|
42
|
-
expect(error_fired).to be_falsey
|
43
|
-
expect(retry_fired).to be_truthy
|
44
|
-
expect(output_count).to eq(1)
|
45
|
-
end
|
46
|
-
|
47
|
-
it "reloads Rails classes (never more than once)" do
|
48
|
-
fake_application = double("Rails.application",
|
49
|
-
config: double("Rails.application.config",
|
50
|
-
cache_classes: false,
|
51
|
-
reload_classes_only_on_change: false),
|
52
|
-
reloader: double)
|
53
|
-
|
54
|
-
allow(Rails).to receive(:application).and_return(fake_application)
|
55
|
-
if Rails::VERSION::MAJOR >= 5
|
56
|
-
expect(Rails.application.reloader).to receive(:reload!).once
|
57
|
-
else
|
58
|
-
expect(ActionDispatch::Reloader).to receive(:prepare!).once
|
59
|
-
expect(ActionDispatch::Reloader).to receive(:cleanup!).once
|
60
|
-
end
|
61
|
-
job = double(job_attrs)
|
62
|
-
|
63
|
-
# Create extra workers to make sure we don't reload multiple times
|
64
|
-
described_class.new(worker_config.dup)
|
65
|
-
described_class.new(worker_config.dup)
|
66
|
-
|
67
|
-
subject.perform(job)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
describe "#log_job" do
|
72
|
-
around do |block|
|
73
|
-
prev_logger = Delayed::Settings.job_detailed_log_format
|
74
|
-
block.call
|
75
|
-
Delayed::Settings.job_detailed_log_format = prev_logger
|
76
|
-
end
|
77
|
-
|
78
|
-
it "has a reasonable default format" do
|
79
|
-
payload = double(perform: nil)
|
80
|
-
job = Delayed::Job.new(payload_object: payload, priority: 25, strand: "test_jobs")
|
81
|
-
short_log_format = subject.log_job(job, :short)
|
82
|
-
expect(short_log_format).to eq("RSpec::Mocks::Double")
|
83
|
-
long_format = subject.log_job(job, :long)
|
84
|
-
expect(long_format).to eq("RSpec::Mocks::Double {\"priority\":25,\"attempts\":0,\"created_at\":null,\"tag\":\"RSpec::Mocks::Double#perform\",\"max_attempts\":null,\"strand\":\"test_jobs\",\"source\":null,\"singleton\":null}") # rubocop:disable Layout/LineLength
|
85
|
-
end
|
86
|
-
|
87
|
-
it "logging format can be changed with settings" do
|
88
|
-
Delayed::Settings.job_detailed_log_format = ->(job) { "override format detailed #{job.strand}" }
|
89
|
-
Delayed::Settings.job_short_log_format = ->(_job) { "override format short" }
|
90
|
-
payload = double(perform: nil)
|
91
|
-
job = Delayed::Job.new(payload_object: payload, priority: 25, strand: "test_jobs")
|
92
|
-
short_log_format = subject.log_job(job, :short)
|
93
|
-
expect(short_log_format).to eq("RSpec::Mocks::Double override format short")
|
94
|
-
long_format = subject.log_job(job, :long)
|
95
|
-
expect(long_format).to eq("RSpec::Mocks::Double override format detailed test_jobs")
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
describe "#run" do
|
100
|
-
it "passes extra config options through to the WorkQueue" do
|
101
|
-
expect(subject.work_queue).to receive(:get_and_lock_next_available)
|
102
|
-
.with(subject.name, worker_config).and_return(nil)
|
103
|
-
subject.run
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
data/spec/sample_jobs.rb
DELETED
@@ -1,79 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class SimpleJob
|
4
|
-
class << self
|
5
|
-
attr_accessor :runs
|
6
|
-
end
|
7
|
-
|
8
|
-
self.runs = 0
|
9
|
-
|
10
|
-
def perform
|
11
|
-
self.class.runs += 1
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
class ErrorJob
|
16
|
-
class << self
|
17
|
-
attr_accessor :runs, :last_error, :failure_runs, :permanent_failure_runs
|
18
|
-
end
|
19
|
-
|
20
|
-
self.runs = 0
|
21
|
-
def perform
|
22
|
-
raise "did not work"
|
23
|
-
end
|
24
|
-
|
25
|
-
self.last_error = nil
|
26
|
-
self.failure_runs = 0
|
27
|
-
def on_failure(error)
|
28
|
-
self.class.last_error = error
|
29
|
-
self.class.failure_runs += 1
|
30
|
-
end
|
31
|
-
|
32
|
-
self.permanent_failure_runs = 0
|
33
|
-
def on_permanent_failure(error)
|
34
|
-
self.class.last_error = error
|
35
|
-
self.class.permanent_failure_runs += 1
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
class UnlockJob
|
40
|
-
attr_accessor :times_to_unlock
|
41
|
-
|
42
|
-
def initialize(times_to_unlock)
|
43
|
-
@times_to_unlock = times_to_unlock
|
44
|
-
end
|
45
|
-
|
46
|
-
def perform
|
47
|
-
raise SystemExit, "raising to trigger on_failure"
|
48
|
-
end
|
49
|
-
|
50
|
-
def on_failure(_error)
|
51
|
-
times_to_unlock -= 1
|
52
|
-
:unlock if times_to_unlock <= 0
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
class LongRunningJob
|
57
|
-
def perform
|
58
|
-
sleep 250
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
module M
|
63
|
-
class ModuleJob
|
64
|
-
class << self
|
65
|
-
attr_accessor :runs
|
66
|
-
end
|
67
|
-
|
68
|
-
cattr_accessor :runs
|
69
|
-
self.runs = 0
|
70
|
-
def perform
|
71
|
-
self.class.runs += 1
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
class DeserializeErrorJob < SimpleJob; end
|
77
|
-
Psych.add_domain_type("ruby/object", "DeserializeErrorJob") do |_type, _val|
|
78
|
-
raise "error deserializing"
|
79
|
-
end
|
@@ -1,105 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
shared_examples_for "Delayed::Batch" do
|
4
|
-
context "batching" do
|
5
|
-
it "batches up all deferrable delayed methods" do
|
6
|
-
later = 1.hour.from_now
|
7
|
-
Delayed::Batch.serial_batch do
|
8
|
-
expect("string".delay(ignore_transaction: true).size).to be true
|
9
|
-
# won't be batched, it'll get its own job
|
10
|
-
expect("string".delay(run_at: later, ignore_transaction: true).reverse).to be_truthy
|
11
|
-
expect("string".delay(ignore_transaction: true).gsub(/./, "!")).to be_truthy
|
12
|
-
end
|
13
|
-
batch_jobs = Delayed::Job.find_available(5)
|
14
|
-
regular_jobs = Delayed::Job.list_jobs(:future, 5)
|
15
|
-
expect(regular_jobs.size).to eq(1)
|
16
|
-
expect(regular_jobs.first.batch?).to be(false)
|
17
|
-
expect(batch_jobs.size).to eq(1)
|
18
|
-
batch_job = batch_jobs.first
|
19
|
-
expect(batch_job.batch?).to be(true)
|
20
|
-
expect(batch_job.payload_object.mode).to eq(:serial)
|
21
|
-
expect(batch_job.payload_object.jobs.map do |j|
|
22
|
-
[j.payload_object.object, j.payload_object.method, j.payload_object.args]
|
23
|
-
end).to eq([
|
24
|
-
[
|
25
|
-
"string", :size, []
|
26
|
-
],
|
27
|
-
[
|
28
|
-
"string", :gsub, [
|
29
|
-
/./, "!"
|
30
|
-
]
|
31
|
-
]
|
32
|
-
])
|
33
|
-
end
|
34
|
-
|
35
|
-
it "does not let you invoke it directly" do
|
36
|
-
Delayed::Batch.serial_batch do
|
37
|
-
expect("string".delay(ignore_transaction: true).size).to be true
|
38
|
-
expect("string".delay(ignore_transaction: true).gsub(/./, "!")).to be true
|
39
|
-
end
|
40
|
-
expect(Delayed::Job.jobs_count(:current)).to eq(1)
|
41
|
-
job = Delayed::Job.find_available(1).first
|
42
|
-
expect { job.invoke_job }.to raise_error(RuntimeError)
|
43
|
-
end
|
44
|
-
|
45
|
-
it "creates valid jobs" do
|
46
|
-
Delayed::Batch.serial_batch do
|
47
|
-
expect("string".delay(ignore_transaction: true).size).to be true
|
48
|
-
expect("string".delay(ignore_transaction: true).gsub(/./, "!")).to be true
|
49
|
-
end
|
50
|
-
expect(Delayed::Job.jobs_count(:current)).to eq(1)
|
51
|
-
|
52
|
-
batch_job = Delayed::Job.find_available(1).first
|
53
|
-
expect(batch_job.batch?).to be(true)
|
54
|
-
jobs = batch_job.payload_object.jobs
|
55
|
-
expect(jobs.size).to eq(2)
|
56
|
-
expect(jobs[0]).to be_new_record
|
57
|
-
expect(jobs[0].payload_object.class).to eq(Delayed::PerformableMethod)
|
58
|
-
expect(jobs[0].payload_object.method).to eq(:size)
|
59
|
-
expect(jobs[0].payload_object.args).to eq([])
|
60
|
-
expect(jobs[0].payload_object.perform).to eq(6)
|
61
|
-
expect(jobs[1]).to be_new_record
|
62
|
-
expect(jobs[1].payload_object.class).to eq(Delayed::PerformableMethod)
|
63
|
-
expect(jobs[1].payload_object.method).to eq(:gsub)
|
64
|
-
expect(jobs[1].payload_object.args).to eq([/./, "!"])
|
65
|
-
expect(jobs[1].payload_object.perform).to eq("!!!!!!")
|
66
|
-
end
|
67
|
-
|
68
|
-
it "creates a different batch for each priority" do
|
69
|
-
Delayed::Batch.serial_batch do
|
70
|
-
expect("string".delay(priority: Delayed::LOW_PRIORITY, ignore_transaction: true).size).to be true
|
71
|
-
expect("string".delay(ignore_transaction: true).gsub(/./, "!")).to be true
|
72
|
-
end
|
73
|
-
expect(Delayed::Job.jobs_count(:current)).to eq(2)
|
74
|
-
end
|
75
|
-
|
76
|
-
it "uses the given priority for all, if specified" do
|
77
|
-
Delayed::Batch.serial_batch(priority: 11) do
|
78
|
-
expect("string".delay(priority: 20, ignore_transaction: true).size).to be true
|
79
|
-
expect("string".delay(priority: 15, ignore_transaction: true).gsub(/./, "!")).to be true
|
80
|
-
end
|
81
|
-
expect(Delayed::Job.jobs_count(:current)).to eq(1)
|
82
|
-
expect(Delayed::Job.find_available(1).first.priority).to eq(11)
|
83
|
-
end
|
84
|
-
|
85
|
-
it "justs create the job, if there's only one in the batch" do
|
86
|
-
Delayed::Batch.serial_batch(priority: 11) do
|
87
|
-
expect("string".delay(ignore_transaction: true).size).to be true
|
88
|
-
end
|
89
|
-
expect(Delayed::Job.jobs_count(:current)).to eq(1)
|
90
|
-
expect(Delayed::Job.find_available(1).first.tag).to eq("String#size")
|
91
|
-
expect(Delayed::Job.find_available(1).first.priority).to eq(11)
|
92
|
-
end
|
93
|
-
|
94
|
-
it "lists a job only once when the same call is made multiple times" do
|
95
|
-
Delayed::Batch.serial_batch(priority: 11) do
|
96
|
-
"string".delay(ignore_transaction: true).size
|
97
|
-
"string".delay(ignore_transaction: true).gsub(/./, "!")
|
98
|
-
"string".delay(ignore_transaction: true).size
|
99
|
-
end
|
100
|
-
batch_job = Delayed::Job.find_available(1).first
|
101
|
-
jobs = batch_job.payload_object.jobs
|
102
|
-
expect(jobs.size).to eq(2)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|