sidekiq-unique-jobs 3.0.14 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sidekiq-unique-jobs might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.editorconfig +14 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +8 -0
- data/.simplecov +12 -0
- data/.travis.yml +16 -8
- data/Appraisals +10 -10
- data/CHANGELOG.md +11 -0
- data/Gemfile +11 -1
- data/README.md +58 -4
- data/Rakefile +2 -1
- data/circle.yml +36 -0
- data/gemfiles/sidekiq_2.17.gemfile +8 -1
- data/gemfiles/sidekiq_3.0.gemfile +8 -1
- data/gemfiles/sidekiq_3.1.gemfile +8 -1
- data/gemfiles/sidekiq_3.2.gemfile +8 -1
- data/gemfiles/sidekiq_3.3.gemfile +8 -1
- data/gemfiles/sidekiq_develop.gemfile +8 -1
- data/lib/sidekiq-unique-jobs.rb +44 -9
- data/lib/sidekiq_unique_jobs/client/middleware.rb +47 -0
- data/lib/sidekiq_unique_jobs/config.rb +9 -33
- data/lib/sidekiq_unique_jobs/core_ext.rb +46 -0
- data/lib/sidekiq_unique_jobs/lock.rb +10 -0
- data/lib/sidekiq_unique_jobs/lock/time_calculator.rb +44 -0
- data/lib/sidekiq_unique_jobs/lock/until_executed.rb +56 -0
- data/lib/sidekiq_unique_jobs/lock/until_executing.rb +6 -0
- data/lib/sidekiq_unique_jobs/lock/until_timeout.rb +10 -0
- data/lib/sidekiq_unique_jobs/lock/while_executing.rb +31 -0
- data/lib/sidekiq_unique_jobs/middleware.rb +30 -14
- data/lib/sidekiq_unique_jobs/normalizer.rb +7 -0
- data/lib/sidekiq_unique_jobs/options_with_fallback.rb +36 -0
- data/lib/sidekiq_unique_jobs/run_lock_failed.rb +1 -0
- data/lib/sidekiq_unique_jobs/scripts.rb +50 -0
- data/lib/sidekiq_unique_jobs/server/middleware.rb +73 -0
- data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +71 -9
- data/lib/sidekiq_unique_jobs/testing.rb +34 -0
- data/lib/sidekiq_unique_jobs/testing/sidekiq_overrides.rb +63 -0
- data/lib/sidekiq_unique_jobs/unique_args.rb +132 -0
- data/lib/sidekiq_unique_jobs/unlockable.rb +26 -0
- data/lib/sidekiq_unique_jobs/version.rb +1 -1
- data/redis/aquire_lock.lua +9 -0
- data/redis/release_lock.lua +14 -0
- data/redis/synchronize.lua +15 -0
- data/sidekiq-unique-jobs.gemspec +2 -4
- data/spec/lib/sidekiq_unique_jobs/client/middleware_spec.rb +195 -0
- data/spec/lib/sidekiq_unique_jobs/core_ext_spec.rb +25 -0
- data/spec/lib/sidekiq_unique_jobs/lock/time_calculator_spec.rb +81 -0
- data/spec/lib/sidekiq_unique_jobs/lock/while_executing_spec.rb +48 -0
- data/spec/lib/sidekiq_unique_jobs/normalizer_spec.rb +21 -0
- data/spec/lib/sidekiq_unique_jobs/scripts_spec.rb +74 -0
- data/spec/lib/sidekiq_unique_jobs/server/middleware_spec.rb +100 -0
- data/spec/lib/{sidekiq_testing_enabled_spec.rb → sidekiq_unique_jobs/sidekiq_testing_enabled_spec.rb} +29 -68
- data/spec/lib/sidekiq_unique_jobs/sidekiq_unique_ext_spec.rb +79 -0
- data/spec/lib/sidekiq_unique_jobs/sidekiq_unique_jobs_spec.rb +36 -0
- data/spec/lib/sidekiq_unique_jobs/unique_args_spec.rb +106 -0
- data/spec/spec_helper.rb +40 -10
- data/spec/support/matchers/redis_matchers.rb +19 -0
- data/spec/support/ruby_meta.rb +10 -0
- data/spec/support/sidekiq_meta.rb +11 -2
- data/spec/support/unique_macros.rb +52 -0
- data/spec/workers/after_unlock_worker.rb +13 -0
- data/spec/{support → workers}/after_yield_worker.rb +6 -2
- data/spec/{support → workers}/another_unique_worker.rb +1 -1
- data/spec/workers/before_yield_worker.rb +9 -0
- data/spec/workers/expiring_worker.rb +4 -0
- data/spec/workers/inline_expiration_worker.rb +8 -0
- data/spec/workers/inline_unlock_order_worker.rb +8 -0
- data/spec/workers/inline_worker.rb +8 -0
- data/spec/workers/just_a_worker.rb +8 -0
- data/spec/workers/main_job.rb +8 -0
- data/spec/workers/my_unique_worker.rb +8 -0
- data/spec/{support → workers}/my_worker.rb +0 -0
- data/spec/workers/plain_class.rb +4 -0
- data/spec/workers/queue_worker.rb +6 -0
- data/spec/workers/queue_worker_with_filter_method.rb +7 -0
- data/spec/workers/queue_worker_with_filter_proc.rb +11 -0
- data/spec/workers/run_lock_with_retries_worker.rb +12 -0
- data/spec/workers/run_lock_worker.rb +7 -0
- data/spec/workers/test_class.rb +4 -0
- data/spec/workers/unique_job_with_filter_method.rb +18 -0
- data/spec/workers/unique_on_all_queues_worker.rb +13 -0
- data/spec/{support → workers}/unique_worker.rb +1 -1
- data/spec/workers/while_executing_worker.rb +13 -0
- metadata +65 -39
- data/lib/sidekiq_unique_jobs/connectors.rb +0 -16
- data/lib/sidekiq_unique_jobs/connectors/redis_pool.rb +0 -11
- data/lib/sidekiq_unique_jobs/connectors/sidekiq_redis.rb +0 -9
- data/lib/sidekiq_unique_jobs/connectors/testing.rb +0 -11
- data/lib/sidekiq_unique_jobs/inline_testing.rb +0 -12
- data/lib/sidekiq_unique_jobs/middleware/client/strategies/testing_inline.rb +0 -25
- data/lib/sidekiq_unique_jobs/middleware/client/strategies/unique.rb +0 -105
- data/lib/sidekiq_unique_jobs/middleware/client/unique_jobs.rb +0 -43
- data/lib/sidekiq_unique_jobs/middleware/server/unique_jobs.rb +0 -69
- data/lib/sidekiq_unique_jobs/payload_helper.rb +0 -42
- data/lib/sidekiq_unique_jobs/sidekiq_test_overrides.rb +0 -101
- data/spec/lib/client_spec.rb +0 -193
- data/spec/lib/middleware/server/unique_jobs_spec.rb +0 -112
- data/spec/lib/sidekiq_unique_ext_spec.rb +0 -70
- data/spec/lib/unlock_order_spec.rb +0 -64
@@ -0,0 +1,14 @@
|
|
1
|
+
local unique_key = KEYS[1]
|
2
|
+
local job_id = ARGV[1]
|
3
|
+
local stored_jid = redis.pcall('get', unique_key)
|
4
|
+
|
5
|
+
if stored_jid then
|
6
|
+
if stored_jid == job_id then
|
7
|
+
return redis.pcall('del', unique_key)
|
8
|
+
else
|
9
|
+
return 0
|
10
|
+
end
|
11
|
+
else
|
12
|
+
return -1
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
local unique_key = KEYS[1]
|
2
|
+
local time = ARGV[1]
|
3
|
+
|
4
|
+
if redis.pcall('set', unique_key, time + 60, 'nx', 'ex', 60) then
|
5
|
+
return 1
|
6
|
+
end
|
7
|
+
|
8
|
+
local stored_time = redis.pcall('get', unique_key)
|
9
|
+
if stored_time and stored_time < time then
|
10
|
+
if redis.call('set', unique_key, time + 60, 'nx', 'ex') then
|
11
|
+
return 1
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
return 0
|
data/sidekiq-unique-jobs.gemspec
CHANGED
@@ -13,11 +13,9 @@ Gem::Specification.new do |gem|
|
|
13
13
|
gem.test_files = `git ls-files -- test/*`.split("\n")
|
14
14
|
gem.name = 'sidekiq-unique-jobs'
|
15
15
|
gem.require_paths = ['lib']
|
16
|
-
gem.post_install_message = '
|
17
|
-
|
18
|
-
gem.version = SidekiqUniqueJobs::VERSION
|
16
|
+
gem.post_install_message = 'WARNING: VERSION 4.0.0 HAS BREAKING CHANGES! Please see Readme for info'
|
17
|
+
gem.version = SidekiqUniqueJobs::VERSION
|
19
18
|
gem.add_dependency 'sidekiq', '>= 2.6'
|
20
|
-
gem.add_development_dependency 'mock_redis'
|
21
19
|
gem.add_development_dependency 'rspec', '~> 3.1.0'
|
22
20
|
gem.add_development_dependency 'rake'
|
23
21
|
gem.add_development_dependency 'rspec-sidekiq'
|
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'celluloid/current'
|
3
|
+
require 'sidekiq/worker'
|
4
|
+
require 'sidekiq-unique-jobs'
|
5
|
+
require 'sidekiq/scheduled'
|
6
|
+
|
7
|
+
RSpec.describe SidekiqUniqueJobs::Client::Middleware do
|
8
|
+
def digest_for(item)
|
9
|
+
SidekiqUniqueJobs::UniqueArgs.digest(item)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'with real redis' do
|
13
|
+
before do
|
14
|
+
Sidekiq.redis = REDIS
|
15
|
+
Sidekiq.redis(&:flushdb)
|
16
|
+
QueueWorker.sidekiq_options unique: nil, unique_expiration: nil
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'when a job is already scheduled' do
|
20
|
+
context '#new_unique_for' do
|
21
|
+
it 'rejects new scheduled jobs with the same argument' do
|
22
|
+
MyUniqueWorker.perform_in(3600, 1)
|
23
|
+
expect(MyUniqueWorker.perform_in(3600, 1)).to eq(nil)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'will run a job in real time with the same arguments' do
|
27
|
+
WhileExecutingWorker.perform_in(3600, 1)
|
28
|
+
expect(WhileExecutingWorker.perform_async(1)).not_to eq(nil)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'schedules new jobs when arguments differ' do
|
32
|
+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20].each do |x|
|
33
|
+
MainJob.perform_in(x.seconds.from_now, x)
|
34
|
+
end
|
35
|
+
|
36
|
+
Sidekiq.redis do |c|
|
37
|
+
count = c.zcount('schedule', -1, Time.now.to_f + 2 * 60)
|
38
|
+
expect(count).to eq(20)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'does not push duplicate messages when configured for unique only' do
|
45
|
+
item = { 'class' => MyUniqueWorker, 'queue' => 'customqueue', 'args' => [1, 2] }
|
46
|
+
10.times { Sidekiq::Client.push(item) }
|
47
|
+
Sidekiq.redis do |c|
|
48
|
+
expect(c.llen('queue:customqueue')).to eq(1)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'does push duplicate messages to different queues' do
|
53
|
+
Sidekiq::Client.push('class' => MyUniqueWorker, 'queue' => 'customqueue', 'args' => [1, 2])
|
54
|
+
Sidekiq::Client.push('class' => MyUniqueWorker, 'queue' => 'customqueue2', 'args' => [1, 2])
|
55
|
+
Sidekiq.redis do |c|
|
56
|
+
expect(c.llen('queue:customqueue')).to eq 1
|
57
|
+
expect(c.llen('queue:customqueue2')).to eq 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'does not queue duplicates when when calling delay' do
|
62
|
+
10.times { PlainClass.delay(unique_lock: :until_executed, unique: true, queue: 'customqueue').run(1) }
|
63
|
+
Sidekiq.redis do |c|
|
64
|
+
expect(c.llen('queue:customqueue')).to eq(1)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'does not schedule duplicates when calling perform_in' do
|
69
|
+
10.times { MyUniqueWorker.perform_in(60, [1, 2]) }
|
70
|
+
Sidekiq.redis do |c|
|
71
|
+
expect(c.zcount('schedule', -1, Time.now.to_f + 2 * 60))
|
72
|
+
.to eq(1)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'enqueues previously scheduled job' do
|
77
|
+
jid = WhileExecutingWorker.perform_in(60 * 60, 1, 2)
|
78
|
+
item = { 'class' => WhileExecutingWorker, 'queue' => 'customqueue', 'args' => [1, 2], 'jid' => jid }
|
79
|
+
|
80
|
+
# time passes and the job is pulled off the schedule:
|
81
|
+
Sidekiq::Client.push(item)
|
82
|
+
|
83
|
+
Sidekiq.redis do |c|
|
84
|
+
expect(c.llen('queue:customqueue')).to eq 1
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'sets an expiration when provided by sidekiq options' do
|
89
|
+
item = { 'class' => ExpiringWorker, 'queue' => 'customqueue', 'args' => [1, 2] }
|
90
|
+
Sidekiq::Client.push(item)
|
91
|
+
|
92
|
+
Sidekiq.redis do |c|
|
93
|
+
expect(c.llen('queue:customqueue')).to eq(1)
|
94
|
+
expect(c.ttl(digest_for(item)))
|
95
|
+
.to eq(ExpiringWorker.get_sidekiq_options['unique_expiration'])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'does push duplicate messages when not configured for unique only' do
|
100
|
+
10.times { Sidekiq::Client.push('class' => QueueWorker, 'queue' => 'customqueue', 'args' => [1, 2]) }
|
101
|
+
|
102
|
+
Sidekiq.redis do |c|
|
103
|
+
expect(c.llen('queue:customqueue')).to eq(10)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe 'when unique_args is defined' do
|
108
|
+
before(:all) { SidekiqUniqueJobs.config.unique_args_enabled = true }
|
109
|
+
after(:all) { SidekiqUniqueJobs.config.unique_args_enabled = false }
|
110
|
+
|
111
|
+
it 'does not push duplicate messages based on args filter method' do
|
112
|
+
expect(QueueWorkerWithFilterMethod).to respond_to(:args_filter)
|
113
|
+
expect(QueueWorkerWithFilterMethod.get_sidekiq_options['unique_args']).to eq :args_filter
|
114
|
+
|
115
|
+
(0..10).each do |i|
|
116
|
+
Sidekiq::Client.push(
|
117
|
+
'class' => QueueWorkerWithFilterMethod,
|
118
|
+
'queue' => 'customqueue',
|
119
|
+
'args' => [1, i]
|
120
|
+
)
|
121
|
+
end
|
122
|
+
|
123
|
+
Sidekiq.redis do |c|
|
124
|
+
expect(c.llen('queue:customqueue')).to eq(1)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'does not push duplicate messages based on args filter proc' do
|
129
|
+
expect(QueueWorkerWithFilterProc.get_sidekiq_options['unique_args']).to be_a(Proc)
|
130
|
+
|
131
|
+
100.times do
|
132
|
+
Sidekiq::Client.push(
|
133
|
+
'class' => QueueWorkerWithFilterProc,
|
134
|
+
'queue' => 'customqueue',
|
135
|
+
'args' => [1, { random: rand, name: 'foobar' }]
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
Sidekiq.redis do |c|
|
140
|
+
expect(c.llen('queue:customqueue')).to eq(1)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe 'when unique_on_all_queues is set' do
|
145
|
+
it 'does not push duplicate messages on different queues' do
|
146
|
+
item = { 'class' => UniqueOnAllQueuesWorker, 'args' => [1, 2] }
|
147
|
+
Sidekiq::Client.push(item.merge('queue' => 'customqueue'))
|
148
|
+
Sidekiq::Client.push(item.merge('queue' => 'customqueue2'))
|
149
|
+
Sidekiq.redis do |c|
|
150
|
+
expect(c.llen('queue:customqueue')).to eq(1)
|
151
|
+
expect(c.llen('queue:customqueue2')).to eq(0)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# TODO: If anyone know of a better way to check that the expiration for scheduled
|
158
|
+
# jobs are set around the same time as the scheduled job itself feel free to improve.
|
159
|
+
it 'expires the digest when a scheduled job is scheduled at' do
|
160
|
+
expected_expires_at =
|
161
|
+
(Time.at(15.minutes.from_now) - Time.now.utc) + SidekiqUniqueJobs.config.default_expiration
|
162
|
+
jid = MyUniqueWorker.perform_in(expected_expires_at, 'mike')
|
163
|
+
item = { 'class' => MyUniqueWorker,
|
164
|
+
'queue' => 'customqueue',
|
165
|
+
'args' => ['mike'],
|
166
|
+
'at' => expected_expires_at }
|
167
|
+
digest = digest_for(item.merge('jid' => jid))
|
168
|
+
Sidekiq.redis do |c|
|
169
|
+
expect(c.ttl(digest)).to eq(9_899)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'logs duplicate payload when config turned on' do
|
174
|
+
expect(Sidekiq.logger).to receive(:warn).with(/^payload is not unique/)
|
175
|
+
UniqueWorker.sidekiq_options log_duplicate_payload: true
|
176
|
+
2.times { Sidekiq::Client.push('class' => UniqueWorker, 'queue' => 'customqueue', 'args' => [1, 2]) }
|
177
|
+
Sidekiq.redis do |c|
|
178
|
+
expect(c.llen('queue:customqueue')).to eq 1
|
179
|
+
end
|
180
|
+
UniqueWorker.sidekiq_options log_duplicate_payload: true
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'does not log duplicate payload when config turned off' do
|
184
|
+
expect(Sidekiq.logger).to_not receive(:warn).with(/^payload is not unique/)
|
185
|
+
|
186
|
+
UniqueWorker.sidekiq_options log_duplicate_payload: false
|
187
|
+
|
188
|
+
2.times { Sidekiq::Client.push('class' => UniqueWorker, 'queue' => 'customqueue', 'args' => [1, 2]) }
|
189
|
+
Sidekiq.redis do |c|
|
190
|
+
expect(c.llen('queue:customqueue')).to eq 1
|
191
|
+
end
|
192
|
+
UniqueWorker.sidekiq_options log_duplicate_payload: true
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Hash do
|
4
|
+
subject { { test: :me, not: :me } }
|
5
|
+
|
6
|
+
describe '#slice' do
|
7
|
+
specify { expect(subject.slice(:test)).to eq(test: :me) }
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#slice!' do
|
11
|
+
specify { expect { subject.slice!(:test) }.to change { subject }.to(test: :me) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec.describe String do
|
16
|
+
describe '#classify' do
|
17
|
+
subject { 'under_scored_string' }
|
18
|
+
its(:classify) { is_expected.to eq('UnderScoredString') }
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#camelize' do
|
22
|
+
subject { 'under_scored_string' }
|
23
|
+
its(:camelize) { is_expected.to eq('UnderScoredString') }
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe SidekiqUniqueJobs::Lock::TimeCalculator do
|
4
|
+
include ActiveSupport::Testing::TimeHelpers
|
5
|
+
shared_context 'undefined worker class' do
|
6
|
+
subject { described_class.new('class' => 'test') }
|
7
|
+
end
|
8
|
+
|
9
|
+
shared_context 'item not scheduled' do
|
10
|
+
subject { described_class.new('class' => 'MyUniqueWorker') }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'public api' do
|
14
|
+
subject { described_class.new(nil) }
|
15
|
+
it { is_expected.to respond_to(:time_until_scheduled) }
|
16
|
+
it { is_expected.to respond_to(:unique_expiration) }
|
17
|
+
it { is_expected.to respond_to(:worker_class_unique_expiration) }
|
18
|
+
it { is_expected.to respond_to(:worker_class) }
|
19
|
+
it { is_expected.to respond_to(:seconds) }
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '.for_item' do
|
23
|
+
it 'initializes a new calculator' do
|
24
|
+
expect(described_class).to receive(:new).with('WAT')
|
25
|
+
described_class.for_item('WAT')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#seconds' do
|
30
|
+
subject { described_class.new(nil) }
|
31
|
+
|
32
|
+
before do
|
33
|
+
allow(subject).to receive(:time_until_scheduled).and_return(10)
|
34
|
+
allow(subject).to receive(:unique_expiration).and_return(9)
|
35
|
+
end
|
36
|
+
its(:seconds) { is_expected.to eq(19) }
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#time_until_scheduled' do
|
40
|
+
it_behaves_like 'item not scheduled' do
|
41
|
+
its(:time_until_scheduled) { is_expected.to eq(0) }
|
42
|
+
end
|
43
|
+
|
44
|
+
subject { described_class.new('class' => 'MyUniqueWorker', 'at' => schedule_time) }
|
45
|
+
let(:schedule_time) { 1.day.from_now.to_i }
|
46
|
+
let(:now_in_utc) { Time.now.utc.to_i }
|
47
|
+
|
48
|
+
its(:time_until_scheduled) do
|
49
|
+
travel_to(Time.at(now_in_utc)) do
|
50
|
+
is_expected.to eq(schedule_time - now_in_utc)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#unique_expiration' do
|
56
|
+
it_behaves_like 'undefined worker class' do
|
57
|
+
its(:unique_expiration) { is_expected.to eq(SidekiqUniqueJobs.config.default_expiration) }
|
58
|
+
end
|
59
|
+
|
60
|
+
subject { described_class.new('class' => 'MyUniqueWorker') }
|
61
|
+
its(:unique_expiration) { is_expected.to eq(7_200) }
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#worker_class_unique_expiration' do
|
65
|
+
it_behaves_like 'undefined worker class' do
|
66
|
+
its(:worker_class_unique_expiration) { is_expected.to eq(nil) }
|
67
|
+
end
|
68
|
+
|
69
|
+
subject { described_class.new('class' => 'MyUniqueWorker') }
|
70
|
+
its(:worker_class_unique_expiration) { is_expected.to eq(7_200) }
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#worker_class' do
|
74
|
+
it_behaves_like 'undefined worker class' do
|
75
|
+
its(:worker_class) { is_expected.to eq('test') }
|
76
|
+
end
|
77
|
+
|
78
|
+
subject { described_class.new('class' => 'MyWorker') }
|
79
|
+
its(:worker_class) { is_expected.to eq(MyWorker) }
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe SidekiqUniqueJobs::Lock::WhileExecuting do
|
4
|
+
it 'allows only one mutex object to have the lock at a time' do
|
5
|
+
mutexes = (1..10).map do
|
6
|
+
described_class.new('test_mutex_key')
|
7
|
+
end
|
8
|
+
|
9
|
+
x = 0
|
10
|
+
mutexes.map do |m|
|
11
|
+
Thread.new do
|
12
|
+
m.synchronize do
|
13
|
+
y = x
|
14
|
+
sleep 0.001
|
15
|
+
x = y + 1
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end.map(&:join)
|
19
|
+
|
20
|
+
expect(x).to eq(10)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'handles auto cleanup correctly' do
|
24
|
+
m = described_class.new('test_mutex_key')
|
25
|
+
|
26
|
+
SidekiqUniqueJobs.connection do |conn|
|
27
|
+
conn.set 'test_mutex_key', Time.now.to_i - 1, nx: true
|
28
|
+
end
|
29
|
+
|
30
|
+
start = Time.now.to_i
|
31
|
+
m.synchronize do
|
32
|
+
'nop'
|
33
|
+
end
|
34
|
+
|
35
|
+
# no longer than a second
|
36
|
+
expect(Time.now.to_i).to be <= start + 1
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'maintains mutex semantics' do
|
40
|
+
m = described_class.new('test_mutex_key')
|
41
|
+
|
42
|
+
expect do
|
43
|
+
m.synchronize do
|
44
|
+
m.synchronize {}
|
45
|
+
end
|
46
|
+
end.to raise_error(ThreadError)
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe SidekiqUniqueJobs::Normalizer do
|
4
|
+
def jsonify(args)
|
5
|
+
described_class.jsonify(args)
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '.jsonify' do
|
9
|
+
specify do
|
10
|
+
original = [1, :test, [test: :test]]
|
11
|
+
expected = [1, 'test', ['test' => 'test']]
|
12
|
+
expect(jsonify(original)).to eq(expected)
|
13
|
+
end
|
14
|
+
|
15
|
+
specify do
|
16
|
+
original = [1, :test, [test: [test: :test]]]
|
17
|
+
expected = [1, 'test', ['test' => ['test' => 'test']]]
|
18
|
+
expect(jsonify(original)).to eq(expected)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
RSpec.describe SidekiqUniqueJobs::Scripts do
|
3
|
+
include ActiveSupport::Testing::TimeHelpers
|
4
|
+
MD5_DIGEST ||= 'unique'.freeze
|
5
|
+
UNIQUE_KEY ||= 'uniquejobs:unique'.freeze
|
6
|
+
JID ||= 'fuckit'.freeze
|
7
|
+
ANOTHER_JID ||= 'anotherjid'.freeze
|
8
|
+
|
9
|
+
context 'class methods' do
|
10
|
+
subject { SidekiqUniqueJobs::Scripts }
|
11
|
+
|
12
|
+
it { is_expected.to respond_to(:call).with(3).arguments }
|
13
|
+
it { is_expected.to respond_to(:logger) }
|
14
|
+
it { is_expected.to respond_to(:script_shas) }
|
15
|
+
it { is_expected.to respond_to(:connection).with(1).arguments }
|
16
|
+
it { is_expected.to respond_to(:script_source).with(1).arguments }
|
17
|
+
it { is_expected.to respond_to(:script_path).with(1).arguments }
|
18
|
+
|
19
|
+
describe '.script_shas' do
|
20
|
+
its(:script_shas) { is_expected.to be_a(Hash) }
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '.logger' do
|
24
|
+
its(:logger) { is_expected.to eq(Sidekiq.logger) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def lock_for(seconds = 1, jid = JID, key = UNIQUE_KEY)
|
28
|
+
subject.call(:aquire_lock, nil, keys: [key], argv: [jid, seconds])
|
29
|
+
end
|
30
|
+
|
31
|
+
def unlock(key = UNIQUE_KEY, jid = JID)
|
32
|
+
subject.call(:release_lock, nil, keys: [key], argv: [jid])
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '.aquire_lock' do
|
36
|
+
context 'when job is unique' do
|
37
|
+
specify { expect(lock_for).to eq(1) }
|
38
|
+
specify do
|
39
|
+
expect(lock_for(0.5.seconds)).to eq(1)
|
40
|
+
expect(Redis)
|
41
|
+
.to have_key(UNIQUE_KEY)
|
42
|
+
.for_seconds(1)
|
43
|
+
.with_value('fuckit')
|
44
|
+
sleep 0.5
|
45
|
+
expect(lock_for).to eq(1)
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when job is locked' do
|
49
|
+
before { expect(lock_for).to eq(1) }
|
50
|
+
specify { expect(lock_for).to eq(0) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '.release_lock' do
|
56
|
+
context 'when job is locked by another jid' do
|
57
|
+
before { expect(lock_for(10.seconds, 'anotherjid')).to eq(1) }
|
58
|
+
specify { expect(unlock).to eq(0) }
|
59
|
+
after { unlock(UNIQUE_KEY, ANOTHER_JID) }
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when job is not locked at all' do
|
63
|
+
specify { expect(unlock).to eq(-1) }
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'when job is locked by the same jid' do
|
67
|
+
specify do
|
68
|
+
expect(lock_for(10.seconds)).to eq(1)
|
69
|
+
expect(unlock).to eq(1)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|