sidekiq-status 0.6.0 → 3.0.3
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/.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
|