sidekiq-status 0.6.0 → 3.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yaml +53 -0
- data/.gitignore +4 -1
- data/.gitlab-ci.yml +17 -0
- data/Appraisals +11 -0
- data/CHANGELOG.md +36 -18
- data/Gemfile +0 -4
- data/README.md +129 -50
- data/Rakefile +2 -0
- data/gemfiles/sidekiq_6.1.gemfile +7 -0
- data/gemfiles/sidekiq_6.x.gemfile +7 -0
- data/gemfiles/sidekiq_7.x.gemfile +7 -0
- data/lib/sidekiq-status/client_middleware.rb +46 -8
- data/lib/sidekiq-status/redis_adapter.rb +18 -0
- data/lib/sidekiq-status/redis_client_adapter.rb +14 -0
- data/lib/sidekiq-status/server_middleware.rb +76 -20
- data/lib/sidekiq-status/sidekiq_extensions.rb +7 -0
- data/lib/sidekiq-status/storage.rb +19 -9
- data/lib/sidekiq-status/testing/inline.rb +10 -0
- data/lib/sidekiq-status/version.rb +1 -1
- data/lib/sidekiq-status/web.rb +116 -25
- data/lib/sidekiq-status/worker.rb +12 -5
- data/lib/sidekiq-status.rb +40 -5
- data/sidekiq-status.gemspec +7 -4
- data/spec/environment.rb +1 -0
- data/spec/lib/sidekiq-status/client_middleware_spec.rb +15 -12
- data/spec/lib/sidekiq-status/server_middleware_spec.rb +66 -22
- data/spec/lib/sidekiq-status/web_spec.rb +62 -15
- data/spec/lib/sidekiq-status/worker_spec.rb +19 -1
- data/spec/lib/sidekiq-status_spec.rb +94 -21
- data/spec/spec_helper.rb +104 -26
- data/spec/support/test_jobs.rb +71 -6
- data/web/sidekiq-status-single-web.png +0 -0
- data/web/views/status.erb +118 -0
- data/web/views/status_not_found.erb +6 -0
- data/web/views/statuses.erb +108 -22
- metadata +74 -13
- data/.travis.yml +0 -16
- data/gemfiles/Gemfile.sidekiq-2 +0 -5
@@ -5,20 +5,19 @@ describe Sidekiq::Status::ServerMiddleware do
|
|
5
5
|
let!(:redis) { Sidekiq.redis { |conn| conn } }
|
6
6
|
let!(:job_id) { SecureRandom.hex(12) }
|
7
7
|
|
8
|
-
|
9
|
-
# Seems like flushall has no effect on recently published messages,
|
10
|
-
# so we should wait till they expire
|
11
|
-
before { redis.flushall; sleep 0.1 }
|
12
|
-
|
13
|
-
describe "#call" do
|
8
|
+
describe "without :expiration parameter" do
|
14
9
|
it "sets working/complete status" do
|
15
|
-
thread = confirmations_thread 4, "status_updates", "job_messages_#{job_id}"
|
16
10
|
allow(SecureRandom).to receive(:hex).once.and_return(job_id)
|
17
11
|
start_server do
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
12
|
+
thread = branched_redis_thread 4, "status_updates", "job_messages_#{job_id}" do
|
13
|
+
expect(ConfirmationJob.perform_async 'arg1' => 'val1').to eq(job_id)
|
14
|
+
end
|
15
|
+
expect(thread.value).to eq([
|
16
|
+
job_id,
|
17
|
+
job_id,
|
18
|
+
"while in #perform, status = working",
|
19
|
+
job_id
|
20
|
+
])
|
22
21
|
end
|
23
22
|
expect(redis.hget("sidekiq:status:#{job_id}", :status)).to eq('complete')
|
24
23
|
expect(Sidekiq::Status::complete?(job_id)).to be_truthy
|
@@ -35,19 +34,64 @@ describe Sidekiq::Status::ServerMiddleware do
|
|
35
34
|
expect(Sidekiq::Status::failed?(job_id)).to be_truthy
|
36
35
|
end
|
37
36
|
|
38
|
-
|
39
|
-
|
37
|
+
it "sets failed status when Exception raised" do
|
38
|
+
allow(SecureRandom).to receive(:hex).once.and_return(job_id)
|
39
|
+
start_server do
|
40
|
+
expect(capture_status_updates(3) {
|
41
|
+
expect(FailingHardJob.perform_async).to eq(job_id)
|
42
|
+
}).to eq([job_id]*3)
|
43
|
+
end
|
44
|
+
expect(redis.hget("sidekiq:status:#{job_id}", :status)).to eq('failed')
|
45
|
+
expect(Sidekiq::Status::failed?(job_id)).to be_truthy
|
46
|
+
end
|
47
|
+
|
48
|
+
context "when Sidekiq::Status::Worker is not included in the job" do
|
49
|
+
it "should not set a failed status" do
|
40
50
|
allow(SecureRandom).to receive(:hex).once.and_return(job_id)
|
41
51
|
start_server do
|
42
|
-
expect(
|
43
|
-
|
44
|
-
|
52
|
+
expect(FailingNoStatusJob.perform_async).to eq(job_id)
|
53
|
+
end
|
54
|
+
expect(redis.hget("sidekiq:status:#{job_id}", :status)).to be_nil
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should not set any status when Exception raised" do
|
58
|
+
allow(SecureRandom).to receive(:hex).once.and_return(job_id)
|
59
|
+
start_server do
|
60
|
+
expect(FailingHardNoStatusJob.perform_async).to eq(job_id)
|
61
|
+
end
|
62
|
+
expect(redis.hget("sidekiq:status:#{job_id}", :status)).to be_nil
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should not set any status on system exit signal" do
|
66
|
+
allow(SecureRandom).to receive(:hex).once.and_return(job_id)
|
67
|
+
start_server do
|
68
|
+
expect(ExitedNoStatusJob.perform_async).to eq(job_id)
|
69
|
+
end
|
70
|
+
expect(redis.hget("sidekiq:status:#{job_id}", :status)).to be_nil
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should not set any status on interrupt signal" do
|
74
|
+
allow(SecureRandom).to receive(:hex).once.and_return(job_id)
|
75
|
+
start_server do
|
76
|
+
expect(InterruptedNoStatusJob.perform_async).to eq(job_id)
|
77
|
+
end
|
78
|
+
expect(redis.hget("sidekiq:status:#{job_id}", :status)).to be_nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "sets interrupted status" do
|
83
|
+
it "on system exit signal" do
|
84
|
+
allow(SecureRandom).to receive(:hex).once.and_return(job_id)
|
85
|
+
start_server do
|
86
|
+
expect(capture_status_updates(3) {
|
87
|
+
expect(ExitedJob.perform_async).to eq(job_id)
|
88
|
+
}).to eq([job_id]*3)
|
45
89
|
end
|
46
90
|
expect(redis.hget("sidekiq:status:#{job_id}", :status)).to eq('interrupted')
|
47
91
|
expect(Sidekiq::Status::interrupted?(job_id)).to be_truthy
|
48
92
|
end
|
49
93
|
|
50
|
-
it "on interrupt signal" do
|
94
|
+
it "on interrupt signal" do
|
51
95
|
allow(SecureRandom).to receive(:hex).once.and_return(job_id)
|
52
96
|
start_server do
|
53
97
|
expect(capture_status_updates(3) {
|
@@ -63,13 +107,13 @@ describe Sidekiq::Status::ServerMiddleware do
|
|
63
107
|
it "sets status hash ttl" do
|
64
108
|
allow(SecureRandom).to receive(:hex).once.and_return(job_id)
|
65
109
|
start_server do
|
66
|
-
expect(StubJob.perform_async
|
110
|
+
expect(StubJob.perform_async 'arg1' => 'val1').to eq(job_id)
|
67
111
|
end
|
68
112
|
expect(1..Sidekiq::Status::DEFAULT_EXPIRY).to cover redis.ttl("sidekiq:status:#{job_id}")
|
69
113
|
end
|
70
114
|
end
|
71
115
|
|
72
|
-
describe ":expiration parameter" do
|
116
|
+
describe "with :expiration parameter" do
|
73
117
|
let(:huge_expiration) { Sidekiq::Status::DEFAULT_EXPIRY * 100 }
|
74
118
|
before do
|
75
119
|
allow(SecureRandom).to receive(:hex).once.and_return(job_id)
|
@@ -77,16 +121,16 @@ describe Sidekiq::Status::ServerMiddleware do
|
|
77
121
|
|
78
122
|
it "overwrites default expiry value" do
|
79
123
|
start_server(:expiration => huge_expiration) do
|
80
|
-
StubJob.perform_async
|
124
|
+
StubJob.perform_async 'arg1' => 'val1'
|
81
125
|
end
|
82
|
-
expect((Sidekiq::Status::DEFAULT_EXPIRY
|
126
|
+
expect((Sidekiq::Status::DEFAULT_EXPIRY-1)..huge_expiration).to cover redis.ttl("sidekiq:status:#{job_id}")
|
83
127
|
end
|
84
128
|
|
85
129
|
it "can be overwritten by worker expiration method" do
|
86
130
|
overwritten_expiration = huge_expiration * 100
|
87
131
|
allow_any_instance_of(StubJob).to receive(:expiration).and_return(overwritten_expiration)
|
88
132
|
start_server(:expiration => huge_expiration) do
|
89
|
-
StubJob.perform_async
|
133
|
+
StubJob.perform_async 'arg1' => 'val1'
|
90
134
|
end
|
91
135
|
expect((huge_expiration+1)..overwritten_expiration).to cover redis.ttl("sidekiq:status:#{job_id}")
|
92
136
|
end
|
@@ -8,30 +8,77 @@ describe 'sidekiq status web' do
|
|
8
8
|
let!(:redis) { Sidekiq.redis { |conn| conn } }
|
9
9
|
let!(:job_id) { SecureRandom.hex(12) }
|
10
10
|
|
11
|
-
# Clean Redis before each test
|
12
|
-
# Seems like flushall has no effect on recently published messages,
|
13
|
-
# so we should wait till they expire
|
14
|
-
before { redis.flushall; sleep 0.1 }
|
15
|
-
|
16
11
|
def app
|
17
12
|
Sidekiq::Web
|
18
13
|
end
|
19
14
|
|
20
|
-
|
15
|
+
before do
|
16
|
+
env 'rack.session', csrf: Base64.urlsafe_encode64('token')
|
21
17
|
client_middleware
|
22
18
|
allow(SecureRandom).to receive(:hex).and_return(job_id)
|
19
|
+
end
|
20
|
+
|
21
|
+
around { |example| start_server(&example) }
|
22
|
+
|
23
|
+
it 'shows the list of jobs in progress' do
|
24
|
+
capture_status_updates(2) do
|
25
|
+
expect(LongJob.perform_async(0.5)).to eq(job_id)
|
26
|
+
end
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
+
get '/statuses'
|
29
|
+
expect(last_response).to be_ok
|
30
|
+
expect(last_response.body).to match(/#{job_id}/)
|
31
|
+
expect(last_response.body).to match(/LongJob/)
|
32
|
+
expect(last_response.body).to match(/working/)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'allows filtering the list of jobs by status' do
|
36
|
+
capture_status_updates(2) do
|
37
|
+
LongJob.perform_async(0.5)
|
38
|
+
end
|
39
|
+
|
40
|
+
get '/statuses?status=working'
|
41
|
+
expect(last_response).to be_ok
|
42
|
+
expect(last_response.body).to match(/#{job_id}/)
|
43
|
+
expect(last_response.body).to match(/LongJob/)
|
44
|
+
expect(last_response.body).to match(/working/)
|
45
|
+
end
|
28
46
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
expect(last_response.body).to match(/LongJob/)
|
33
|
-
expect(last_response.body).to match(/working/)
|
47
|
+
it 'allows filtering the list of jobs by completed status' do
|
48
|
+
capture_status_updates(2) do
|
49
|
+
LongJob.perform_async(0.5)
|
34
50
|
end
|
51
|
+
get '/statuses?status=completed'
|
52
|
+
expect(last_response).to be_ok
|
53
|
+
expect(last_response.body).to_not match(/LongJob/)
|
35
54
|
end
|
36
55
|
|
56
|
+
it 'shows a single job in progress' do
|
57
|
+
capture_status_updates(2) do
|
58
|
+
LongJob.perform_async(1, 'another argument')
|
59
|
+
end
|
60
|
+
|
61
|
+
get "/statuses/#{job_id}"
|
62
|
+
expect(last_response).to be_ok
|
63
|
+
expect(last_response.body).to match(/#{job_id}/)
|
64
|
+
expect(last_response.body).to match(/1,"another argument"/)
|
65
|
+
expect(last_response.body).to match(/working/)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'shows custom data for a single job' do
|
69
|
+
capture_status_updates(3) do
|
70
|
+
CustomDataJob.perform_async
|
71
|
+
end
|
72
|
+
|
73
|
+
get "/statuses/#{job_id}"
|
74
|
+
expect(last_response).to be_ok
|
75
|
+
expect(last_response.body).to match(/mister_cat/)
|
76
|
+
expect(last_response.body).to match(/meow/)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'show an error when the requested job ID is not found' do
|
80
|
+
get '/statuses/12345'
|
81
|
+
expect(last_response).to be_not_found
|
82
|
+
expect(last_response.body).to match(/That job can't be found/)
|
83
|
+
end
|
37
84
|
end
|
@@ -13,11 +13,29 @@ describe Sidekiq::Status::Worker do
|
|
13
13
|
|
14
14
|
describe ".expiration" do
|
15
15
|
subject { StubJob.new }
|
16
|
-
|
16
|
+
|
17
17
|
it "allows to set/get expiration" do
|
18
18
|
expect(subject.expiration).to be_nil
|
19
19
|
subject.expiration = :val
|
20
20
|
expect(subject.expiration).to eq(:val)
|
21
21
|
end
|
22
22
|
end
|
23
|
+
|
24
|
+
describe ".at" do
|
25
|
+
subject { StubJob.new }
|
26
|
+
|
27
|
+
it "records when the worker has started" do
|
28
|
+
expect { subject.at(0) }.to(change { subject.retrieve('working_at') })
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when setting the total for the worker" do
|
32
|
+
it "records when the worker has started" do
|
33
|
+
expect { subject.total(100) }.to(change { subject.retrieve('working_at') })
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "records when the worker last worked" do
|
38
|
+
expect { subject.at(0) }.to(change { subject.retrieve('update_time') })
|
39
|
+
end
|
40
|
+
end
|
23
41
|
end
|
@@ -8,11 +8,7 @@ describe Sidekiq::Status do
|
|
8
8
|
let!(:unused_id) { SecureRandom.hex(12) }
|
9
9
|
let!(:plain_sidekiq_job_id) { SecureRandom.hex(12) }
|
10
10
|
let!(:retried_job_id) { SecureRandom.hex(12) }
|
11
|
-
|
12
|
-
# Clean Redis before each test
|
13
|
-
# Seems like flushall has no effect on recently published messages,
|
14
|
-
# so we should wait till they expire
|
15
|
-
before { redis.flushall; sleep 0.1 }
|
11
|
+
let!(:retry_and_fail_job_id) { SecureRandom.hex(12) }
|
16
12
|
|
17
13
|
describe ".status, .working?, .complete?" do
|
18
14
|
it "gets job status by id as symbol" do
|
@@ -20,11 +16,12 @@ describe Sidekiq::Status do
|
|
20
16
|
|
21
17
|
start_server do
|
22
18
|
expect(capture_status_updates(2) {
|
23
|
-
expect(LongJob.perform_async(
|
19
|
+
expect(LongJob.perform_async(0.5)).to eq(job_id)
|
24
20
|
}).to eq([job_id]*2)
|
25
21
|
expect(Sidekiq::Status.status(job_id)).to eq(:working)
|
26
22
|
expect(Sidekiq::Status.working?(job_id)).to be_truthy
|
27
23
|
expect(Sidekiq::Status::queued?(job_id)).to be_falsey
|
24
|
+
expect(Sidekiq::Status::retrying?(job_id)).to be_falsey
|
28
25
|
expect(Sidekiq::Status::failed?(job_id)).to be_falsey
|
29
26
|
expect(Sidekiq::Status::complete?(job_id)).to be_falsey
|
30
27
|
expect(Sidekiq::Status::stopped?(job_id)).to be_falsey
|
@@ -54,9 +51,9 @@ describe Sidekiq::Status do
|
|
54
51
|
allow(SecureRandom).to receive(:hex).once.and_return(job_id)
|
55
52
|
|
56
53
|
start_server do
|
57
|
-
expect(capture_status_updates(
|
54
|
+
expect(capture_status_updates(4) {
|
58
55
|
expect(ProgressJob.perform_async).to eq(job_id)
|
59
|
-
}).to eq([job_id]*
|
56
|
+
}).to eq([job_id]*4)
|
60
57
|
end
|
61
58
|
expect(Sidekiq::Status.at(job_id)).to be(100)
|
62
59
|
expect(Sidekiq::Status.total(job_id)).to be(500)
|
@@ -72,7 +69,7 @@ describe Sidekiq::Status do
|
|
72
69
|
|
73
70
|
start_server do
|
74
71
|
expect(capture_status_updates(2) {
|
75
|
-
expect(LongJob.perform_async(
|
72
|
+
expect(LongJob.perform_async(0.5)).to eq(job_id)
|
76
73
|
}).to eq([job_id]*2)
|
77
74
|
expect(hash = Sidekiq::Status.get_all(job_id)).to include 'status' => 'working'
|
78
75
|
expect(hash).to include 'update_time'
|
@@ -82,6 +79,23 @@ describe Sidekiq::Status do
|
|
82
79
|
end
|
83
80
|
end
|
84
81
|
|
82
|
+
describe '.delete' do
|
83
|
+
it 'deletes the status hash for given job id' do
|
84
|
+
allow(SecureRandom).to receive(:hex).once.and_return(job_id)
|
85
|
+
start_server do
|
86
|
+
expect(capture_status_updates(2) {
|
87
|
+
expect(LongJob.perform_async(0.5)).to eq(job_id)
|
88
|
+
}).to eq([job_id]*2)
|
89
|
+
end
|
90
|
+
expect(Sidekiq::Status.delete(job_id)).to eq(1)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should not raise error while deleting status hash if invalid job id' do
|
94
|
+
allow(SecureRandom).to receive(:hex).once.and_return(job_id)
|
95
|
+
expect(Sidekiq::Status.delete(job_id)).to eq(0)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
85
99
|
describe ".cancel" do
|
86
100
|
it "cancels a job by id" do
|
87
101
|
allow(SecureRandom).to receive(:hex).twice.and_return(job_id, job_id_1)
|
@@ -91,7 +105,7 @@ describe Sidekiq::Status do
|
|
91
105
|
second_job = LongJob.perform_in(3600)
|
92
106
|
expect(second_job).to eq(job_id_1)
|
93
107
|
|
94
|
-
initial_schedule = redis.zrange "schedule", 0, -1,
|
108
|
+
initial_schedule = redis.zrange "schedule", 0, -1, withscores: true
|
95
109
|
expect(initial_schedule.size).to be(2)
|
96
110
|
expect(initial_schedule.select {|scheduled_job| JSON.parse(scheduled_job[0])["jid"] == job_id }.size).to be(1)
|
97
111
|
|
@@ -99,7 +113,7 @@ describe Sidekiq::Status do
|
|
99
113
|
# Unused, therefore unfound => false
|
100
114
|
expect(Sidekiq::Status.cancel(unused_id)).to be_falsey
|
101
115
|
|
102
|
-
remaining_schedule = redis.zrange "schedule", 0, -1,
|
116
|
+
remaining_schedule = redis.zrange "schedule", 0, -1, withscores: true
|
103
117
|
expect(remaining_schedule.size).to be(initial_schedule.size - 1)
|
104
118
|
expect(remaining_schedule.select {|scheduled_job| JSON.parse(scheduled_job[0])["jid"] == job_id }.size).to be(0)
|
105
119
|
end
|
@@ -112,14 +126,14 @@ describe Sidekiq::Status do
|
|
112
126
|
returned_job_id = LongJob.perform_at(scheduled_time)
|
113
127
|
expect(returned_job_id).to eq(job_id)
|
114
128
|
|
115
|
-
initial_schedule = redis.zrange "schedule", 0, -1,
|
129
|
+
initial_schedule = redis.zrange "schedule", 0, -1, withscores: true
|
116
130
|
expect(initial_schedule.size).to be(1)
|
117
131
|
# wrong time, therefore unfound => false
|
118
132
|
expect(Sidekiq::Status.cancel(returned_job_id, (scheduled_time + 1))).to be_falsey
|
119
|
-
expect((redis.zrange "schedule", 0, -1,
|
133
|
+
expect((redis.zrange "schedule", 0, -1, withscores: true).size).to be(1)
|
120
134
|
# same id, same time, deletes
|
121
135
|
expect(Sidekiq::Status.cancel(returned_job_id, (scheduled_time))).to be_truthy
|
122
|
-
expect(redis.zrange "schedule", 0, -1,
|
136
|
+
expect(redis.zrange "schedule", 0, -1, withscores: true).to be_empty
|
123
137
|
end
|
124
138
|
end
|
125
139
|
end
|
@@ -134,14 +148,63 @@ describe Sidekiq::Status do
|
|
134
148
|
expect_2_jobs_ttl_covers 1..Sidekiq::Status::DEFAULT_EXPIRY
|
135
149
|
end
|
136
150
|
|
151
|
+
it "does jobs without a known class" do
|
152
|
+
seed_secure_random_with_job_ids
|
153
|
+
start_server(:expiration => expiration_param) do
|
154
|
+
expect {
|
155
|
+
Sidekiq::Client.new(Sidekiq.redis_pool).
|
156
|
+
push("class" => "NotAKnownClass", "args" => [])
|
157
|
+
}.to_not raise_error
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
137
161
|
it "retries failed jobs" do
|
138
|
-
allow(SecureRandom).to receive(:hex).
|
162
|
+
allow(SecureRandom).to receive(:hex).exactly(3).times.and_return(retried_job_id)
|
139
163
|
start_server do
|
140
|
-
expect(capture_status_updates(
|
164
|
+
expect(capture_status_updates(3) {
|
141
165
|
expect(RetriedJob.perform_async()).to eq(retried_job_id)
|
142
|
-
}).to eq([retried_job_id] *
|
166
|
+
}).to eq([retried_job_id] * 3)
|
167
|
+
expect(Sidekiq::Status.status(retried_job_id)).to eq(:retrying)
|
168
|
+
expect(Sidekiq::Status.working?(retried_job_id)).to be_falsey
|
169
|
+
expect(Sidekiq::Status::queued?(retried_job_id)).to be_falsey
|
170
|
+
expect(Sidekiq::Status::retrying?(retried_job_id)).to be_truthy
|
171
|
+
expect(Sidekiq::Status::failed?(retried_job_id)).to be_falsey
|
172
|
+
expect(Sidekiq::Status::complete?(retried_job_id)).to be_falsey
|
173
|
+
expect(Sidekiq::Status::stopped?(retried_job_id)).to be_falsey
|
174
|
+
expect(Sidekiq::Status::interrupted?(retried_job_id)).to be_falsey
|
175
|
+
end
|
176
|
+
expect(Sidekiq::Status.status(retried_job_id)).to eq(:retrying)
|
177
|
+
expect(Sidekiq::Status::retrying?(retried_job_id)).to be_truthy
|
178
|
+
|
179
|
+
# restarting and waiting for the job to complete
|
180
|
+
start_server do
|
181
|
+
expect(capture_status_updates(3) {}).to eq([retried_job_id] * 3)
|
182
|
+
expect(Sidekiq::Status.status(retried_job_id)).to eq(:complete)
|
183
|
+
expect(Sidekiq::Status.complete?(retried_job_id)).to be_truthy
|
184
|
+
expect(Sidekiq::Status::retrying?(retried_job_id)).to be_falsey
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
it "marks retried jobs as failed once they do eventually fail" do
|
189
|
+
allow(SecureRandom).to receive(:hex).and_return(retry_and_fail_job_id)
|
190
|
+
start_server do
|
191
|
+
expect(
|
192
|
+
capture_status_updates(3) {
|
193
|
+
expect(RetryAndFailJob.perform_async).to eq(retry_and_fail_job_id)
|
194
|
+
}
|
195
|
+
).to eq([retry_and_fail_job_id] * 3)
|
196
|
+
|
197
|
+
expect(Sidekiq::Status.status(retry_and_fail_job_id)).to eq(:retrying)
|
198
|
+
end
|
199
|
+
|
200
|
+
# restarting and waiting for the job to fail
|
201
|
+
start_server do
|
202
|
+
expect(capture_status_updates(3) {}).to eq([retry_and_fail_job_id] * 3)
|
203
|
+
|
204
|
+
expect(Sidekiq::Status.status(retry_and_fail_job_id)).to eq(:failed)
|
205
|
+
expect(Sidekiq::Status.failed?(retry_and_fail_job_id)).to be_truthy
|
206
|
+
expect(Sidekiq::Status::retrying?(retry_and_fail_job_id)).to be_falsey
|
143
207
|
end
|
144
|
-
expect(Sidekiq::Status.status(retried_job_id)).to eq(:complete)
|
145
208
|
end
|
146
209
|
|
147
210
|
context ":expiration param" do
|
@@ -154,7 +217,7 @@ describe Sidekiq::Status do
|
|
154
217
|
expect_2_jobs_ttl_covers (Sidekiq::Status::DEFAULT_EXPIRY+1)..expiration_param
|
155
218
|
end
|
156
219
|
|
157
|
-
it "allow to overwrite :expiration parameter by
|
220
|
+
it "allow to overwrite :expiration parameter by #expiration method from worker" do
|
158
221
|
overwritten_expiration = expiration_param * 100
|
159
222
|
allow_any_instance_of(NoStatusConfirmationJob).to receive(:expiration).
|
160
223
|
and_return(overwritten_expiration)
|
@@ -164,6 +227,16 @@ describe Sidekiq::Status do
|
|
164
227
|
expect_2_jobs_are_done_and_status_eq :complete
|
165
228
|
expect_2_jobs_ttl_covers (expiration_param+1)..overwritten_expiration
|
166
229
|
end
|
230
|
+
|
231
|
+
it "reads #expiration from a method when defined" do
|
232
|
+
allow(SecureRandom).to receive(:hex).once.and_return(job_id, job_id_1)
|
233
|
+
start_server do
|
234
|
+
expect(StubJob.perform_async).to eq(job_id)
|
235
|
+
expect(ExpiryJob.perform_async).to eq(job_id_1)
|
236
|
+
expect(redis.ttl("sidekiq:status:#{job_id}")).to eq(30 * 60)
|
237
|
+
expect(redis.ttl("sidekiq:status:#{job_id_1}")).to eq(15)
|
238
|
+
end
|
239
|
+
end
|
167
240
|
end
|
168
241
|
|
169
242
|
def seed_secure_random_with_job_ids
|
@@ -173,12 +246,12 @@ describe Sidekiq::Status do
|
|
173
246
|
|
174
247
|
def run_2_jobs!
|
175
248
|
start_server(:expiration => expiration_param) do
|
176
|
-
expect(capture_status_updates(
|
249
|
+
expect(capture_status_updates(6) {
|
177
250
|
expect(StubJob.perform_async).to eq(plain_sidekiq_job_id)
|
178
251
|
NoStatusConfirmationJob.perform_async(1)
|
179
252
|
expect(StubJob.perform_async).to eq(job_id_1)
|
180
253
|
NoStatusConfirmationJob.perform_async(2)
|
181
|
-
}).to match_array([plain_sidekiq_job_id, job_id_1] *
|
254
|
+
}).to match_array([plain_sidekiq_job_id, job_id_1] * 3)
|
182
255
|
end
|
183
256
|
end
|
184
257
|
|
data/spec/spec_helper.rb
CHANGED
@@ -1,71 +1,149 @@
|
|
1
1
|
require "rspec"
|
2
|
-
|
3
|
-
require 'celluloid'
|
2
|
+
require 'colorize'
|
4
3
|
require 'sidekiq'
|
4
|
+
|
5
|
+
# Celluloid should only be manually required before Sidekiq versions 4.+
|
6
|
+
require 'sidekiq/version'
|
7
|
+
require 'celluloid' if Gem::Version.new(Sidekiq::VERSION) < Gem::Version.new('4.0')
|
8
|
+
|
5
9
|
require 'sidekiq/processor'
|
6
10
|
require 'sidekiq/manager'
|
7
11
|
require 'sidekiq-status'
|
8
12
|
|
13
|
+
# Clears jobs before every test
|
14
|
+
RSpec.configure do |config|
|
15
|
+
config.before(:each) do
|
16
|
+
Sidekiq.redis { |conn| conn.flushall }
|
17
|
+
client_middleware
|
18
|
+
sleep 0.05
|
19
|
+
end
|
20
|
+
end
|
9
21
|
|
10
22
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
11
23
|
|
12
|
-
|
24
|
+
# Configures client middleware
|
25
|
+
def client_middleware client_middleware_options = {}
|
13
26
|
Sidekiq.configure_client do |config|
|
14
|
-
|
15
|
-
chain.add Sidekiq::Status::ClientMiddleware, client_middleware_options
|
16
|
-
end
|
27
|
+
Sidekiq::Status.configure_client_middleware config, client_middleware_options
|
17
28
|
end
|
18
29
|
end
|
19
30
|
|
20
|
-
def
|
31
|
+
def redis_thread messages_limit, *channels
|
32
|
+
|
21
33
|
parent = Thread.current
|
22
34
|
thread = Thread.new {
|
23
|
-
|
35
|
+
messages = []
|
24
36
|
Sidekiq.redis do |conn|
|
25
|
-
|
37
|
+
puts "Subscribing to #{channels} for #{messages_limit.to_s.bold} messages".cyan if ENV['DEBUG']
|
38
|
+
conn.subscribe_with_timeout 60, *channels do |on|
|
26
39
|
on.subscribe do |ch, subscriptions|
|
40
|
+
puts "Subscribed to #{ch}".cyan if ENV['DEBUG']
|
27
41
|
if subscriptions == channels.size
|
28
42
|
sleep 0.1 while parent.status != "sleep"
|
29
43
|
parent.run
|
30
44
|
end
|
31
45
|
end
|
32
46
|
on.message do |ch, msg|
|
33
|
-
|
34
|
-
|
47
|
+
puts "Message received: #{ch} -> #{msg}".white if ENV['DEBUG']
|
48
|
+
messages << msg
|
49
|
+
conn.unsubscribe if messages.length >= messages_limit
|
35
50
|
end
|
36
51
|
end
|
37
52
|
end
|
38
|
-
|
53
|
+
puts "Returing from thread".cyan if ENV['DEBUG']
|
54
|
+
messages
|
39
55
|
}
|
56
|
+
|
40
57
|
Thread.stop
|
41
58
|
yield if block_given?
|
42
59
|
thread
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def redis_client_thread message_limit, *channels
|
64
|
+
thread = Thread.new {
|
65
|
+
messages = []
|
66
|
+
Sidekiq.redis do |conn|
|
67
|
+
puts "Subscribing to #{channels} for #{message_limit.to_s.bold} messages".cyan if ENV['DEBUG']
|
68
|
+
pubsub = conn.pubsub
|
69
|
+
pubsub.call("SUBSCRIBE", *channels)
|
70
|
+
|
71
|
+
timeouts = 0
|
72
|
+
loop do
|
73
|
+
type, ch, msg = pubsub.next_event(2)
|
74
|
+
next if type == "subscribe"
|
75
|
+
if msg
|
76
|
+
puts "Message received: #{ch} -> #{msg}".white if ENV['DEBUG']
|
77
|
+
messages << msg
|
78
|
+
break if messages.length >= message_limit
|
79
|
+
else
|
80
|
+
# no new message was received in the allocated timeout
|
81
|
+
timeouts += 1
|
82
|
+
break if timeouts >= 30
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
puts "Returing from thread".cyan if ENV['DEBUG']
|
87
|
+
messages
|
88
|
+
}
|
89
|
+
sleep 0.1
|
90
|
+
yield if block_given?
|
91
|
+
thread.join
|
92
|
+
end
|
93
|
+
|
94
|
+
def branched_redis_thread n, *channels, &block
|
95
|
+
if Sidekiq.major_version < 7
|
96
|
+
redis_thread(n, *channels, &block)
|
97
|
+
else
|
98
|
+
redis_client_thread(n, *channels, &block)
|
99
|
+
end
|
43
100
|
end
|
44
101
|
|
45
|
-
def capture_status_updates
|
46
|
-
|
102
|
+
def capture_status_updates n, &block
|
103
|
+
branched_redis_thread(n, "status_updates", &block).value
|
47
104
|
end
|
48
105
|
|
49
|
-
|
106
|
+
# Configures server middleware and launches a sidekiq server
|
107
|
+
def start_server server_middleware_options = {}
|
108
|
+
|
109
|
+
# Creates a process for the Sidekiq server
|
50
110
|
pid = Process.fork do
|
51
|
-
|
52
|
-
|
111
|
+
|
112
|
+
# Redirect the server's outputs
|
113
|
+
$stdout.reopen File::NULL, 'w' unless ENV['DEBUG']
|
114
|
+
$stderr.reopen File::NULL, 'w' unless ENV['DEBUG']
|
115
|
+
|
116
|
+
# Load and configure server options
|
53
117
|
require 'sidekiq/cli'
|
54
|
-
|
55
|
-
|
118
|
+
|
119
|
+
# Add the server middleware
|
56
120
|
Sidekiq.configure_server do |config|
|
57
|
-
config.
|
58
|
-
config.
|
59
|
-
|
60
|
-
end
|
121
|
+
config.concurrency = 5
|
122
|
+
config.redis = Sidekiq::RedisConnection.create if Sidekiq.major_version < 7
|
123
|
+
Sidekiq::Status.configure_server_middleware config, server_middleware_options
|
61
124
|
end
|
62
|
-
|
125
|
+
|
126
|
+
# Launch
|
127
|
+
puts "Server starting".yellow if ENV['DEBUG']
|
128
|
+
instance = Sidekiq::CLI.instance
|
129
|
+
instance.parse(['-r', File.expand_path('environment.rb', File.dirname(__FILE__))])
|
130
|
+
instance.run
|
131
|
+
|
63
132
|
end
|
64
133
|
|
134
|
+
# Run the client-side code
|
65
135
|
yield
|
66
|
-
|
136
|
+
|
137
|
+
# Pause to ensure all jobs are picked up & started before TERM is sent
|
138
|
+
sleep 0.2
|
139
|
+
|
140
|
+
# Attempt to shut down the server normally
|
67
141
|
Process.kill 'TERM', pid
|
68
|
-
|
142
|
+
Process.wait pid
|
143
|
+
|
69
144
|
ensure
|
145
|
+
|
146
|
+
# Ensure the server is actually dead
|
70
147
|
Process.kill 'KILL', pid rescue "OK" # it's OK if the process is gone already
|
148
|
+
|
71
149
|
end
|