sidekiq-unique-jobs 5.0.0 → 5.0.1

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.

Potentially problematic release.


This version of sidekiq-unique-jobs might be problematic. Click here for more details.

Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +2 -31
  3. data/.editorconfig +2 -2
  4. data/.rubocop.yml +1 -1
  5. data/.travis.yml +4 -4
  6. data/CHANGELOG.md +7 -0
  7. data/README.md +2 -6
  8. data/lib/sidekiq-unique-jobs.rb +5 -1
  9. data/lib/sidekiq/simulator.rb +3 -4
  10. data/lib/sidekiq_unique_jobs/cli.rb +27 -5
  11. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +6 -14
  12. data/lib/sidekiq_unique_jobs/scripts.rb +8 -6
  13. data/lib/sidekiq_unique_jobs/scripts/acquire_lock.rb +45 -0
  14. data/lib/sidekiq_unique_jobs/scripts/release_lock.rb +47 -0
  15. data/lib/sidekiq_unique_jobs/testing.rb +1 -1
  16. data/lib/sidekiq_unique_jobs/unique_args.rb +3 -3
  17. data/lib/sidekiq_unique_jobs/unlockable.rb +4 -18
  18. data/lib/sidekiq_unique_jobs/util.rb +21 -2
  19. data/lib/sidekiq_unique_jobs/version.rb +1 -1
  20. data/rails_example/.env +1 -1
  21. data/rails_example/Gemfile +1 -0
  22. data/rails_example/Procfile +1 -1
  23. data/rails_example/app/workers/simple_worker.rb +1 -1
  24. data/rails_example/app/workers/slow_until_executing_worker.rb +1 -1
  25. data/rails_example/app/workers/spawn_simple_worker.rb +1 -1
  26. data/rails_example/app/workers/while_executing_worker.rb +12 -0
  27. data/rails_example/app/workers/without_argument_worker.rb +1 -1
  28. data/sidekiq-unique-jobs.gemspec +1 -0
  29. data/spec/jobs/another_unique_job.rb +1 -1
  30. data/spec/jobs/my_job.rb +1 -1
  31. data/spec/jobs/unique_job_with_filter_method.rb +1 -1
  32. data/spec/jobs/unique_on_all_queues_job.rb +1 -1
  33. data/spec/jobs/until_executed_job.rb +1 -1
  34. data/spec/jobs/while_executing_job.rb +1 -1
  35. data/spec/lib/sidekiq_unique_jobs/cli_spec.rb +183 -0
  36. data/spec/lib/sidekiq_unique_jobs/client/middleware_spec.rb +1 -9
  37. data/spec/lib/sidekiq_unique_jobs/lock/until_and_while_executing_spec.rb +0 -2
  38. data/spec/lib/sidekiq_unique_jobs/script_mock_spec.rb +1 -3
  39. data/spec/lib/sidekiq_unique_jobs/scripts/acquire_lock_spec.rb +50 -0
  40. data/spec/lib/sidekiq_unique_jobs/scripts/release_lock_spec.rb +41 -0
  41. data/spec/lib/sidekiq_unique_jobs/scripts_spec.rb +4 -8
  42. data/spec/lib/sidekiq_unique_jobs/server/middleware_spec.rb +0 -5
  43. data/spec/lib/sidekiq_unique_jobs/sidekiq_testing_enabled_spec.rb +0 -19
  44. data/spec/lib/sidekiq_unique_jobs/sidekiq_unique_ext_spec.rb +0 -5
  45. data/spec/lib/sidekiq_unique_jobs/unlockable_spec.rb +0 -1
  46. data/spec/lib/sidekiq_unique_jobs/util_spec.rb +54 -26
  47. data/spec/spec_helper.rb +40 -2
  48. data/spec/support/matchers/redis_matchers.rb +16 -6
  49. metadata +22 -2
@@ -1,3 +1,3 @@
1
1
  module SidekiqUniqueJobs
2
- VERSION = '5.0.0'.freeze
2
+ VERSION = '5.0.1'.freeze
3
3
  end
@@ -3,7 +3,7 @@ APP_DOMAIN=localhost
3
3
  APP_WEB_URL=http://localhost:3000
4
4
  APP_CABLE_URL=ws://localhost:28080
5
5
  WEB_CONSOLE_WHITELISTED_IPS=127.0.0.1 ::1 127.0.0.0/8 ::1
6
- DB_HOST=postgres
6
+ DB_HOST=localhost
7
7
  DB_PORT=5432
8
8
  DB_USERNAME=mhenrixon
9
9
  DB_PASSWORD=
@@ -25,6 +25,7 @@ gem 'sidekiq-unique-jobs', path: '../'
25
25
  gem 'sinatra', github: 'sinatra/sinatra'
26
26
 
27
27
  group :development do
28
+ gem 'foreman'
28
29
  gem 'web-console'
29
30
  end
30
31
 
@@ -1,2 +1,2 @@
1
1
  web: bundle exec rails server -p $PORT
2
- worker: bundle exec sidekiq
2
+ worker: bundle exec sidekiq -C config/sidekiq.yml
@@ -8,7 +8,7 @@ class SimpleWorker
8
8
 
9
9
  def perform(some_args)
10
10
  Sidekiq::Logging.with_context(self.class.name) do
11
- Sidekiq.logger.debug { "#{__method__}(#{some_args})" }
11
+ SidekiqUniqueJobs.logger.debug { "#{__method__}(#{some_args})" }
12
12
  end
13
13
  sleep 1
14
14
  end
@@ -8,7 +8,7 @@ class SlowUntilExecutingWorker
8
8
 
9
9
  def perform(some_args)
10
10
  Sidekiq::Logging.with_context(self.class.name) do
11
- Sidekiq.logger.debug { "#{__method__}(#{some_args})" }
11
+ SidekiqUniqueJobs.logger.debug { "#{__method__}(#{some_args})" }
12
12
  end
13
13
  sleep 15
14
14
  end
@@ -3,7 +3,7 @@ class SpawnSimpleWorker
3
3
 
4
4
  def perform(spawn_arg)
5
5
  Sidekiq::Logging.with_context(self.class.name) do
6
- Sidekiq.logger.debug { "#{__method__}(#{spawn_arg})" }
6
+ logger.debug { "#{__method__}(#{spawn_arg})" }
7
7
  end
8
8
  SimpleWorker.perform_async spawn_arg
9
9
  end
@@ -0,0 +1,12 @@
1
+ class WhileExecutingWorker
2
+ include Sidekiq::Worker
3
+
4
+ sidekiq_options unique: :while_executing
5
+
6
+ def perform(_one, _two)
7
+ Sidekiq::Logging.with_context(self.class.name) do
8
+ logger.debug { "#{__method__}(#{some_args})" }
9
+ end
10
+ sleep 10
11
+ end
12
+ end
@@ -5,7 +5,7 @@ class WithoutArgumentWorker
5
5
 
6
6
  def perform
7
7
  Sidekiq::Logging.with_context(self.class.name) do
8
- Sidekiq.logger.debug { __method__.to_s }
8
+ logger.debug { __method__.to_s }
9
9
  end
10
10
  sleep 20
11
11
  end
@@ -23,5 +23,6 @@ Gem::Specification.new do |gem|
23
23
  gem.add_development_dependency 'timecop'
24
24
  gem.add_development_dependency 'yard'
25
25
  gem.add_development_dependency 'gem-release'
26
+ gem.add_development_dependency 'aruba'
26
27
  gem.add_development_dependency 'codeclimate-test-reporter', '~> 1.0.0'
27
28
  end
@@ -4,7 +4,7 @@ class AnotherUniqueJob
4
4
  unique: :until_executed
5
5
 
6
6
  sidekiq_retries_exhausted do |msg|
7
- Sidekiq.logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
7
+ logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
8
8
  end
9
9
 
10
10
  def perform(*)
@@ -3,7 +3,7 @@ class MyJob
3
3
  sidekiq_options queue: :working, retry: 1, backtrace: 10, unique: false
4
4
 
5
5
  sidekiq_retries_exhausted do |msg|
6
- Sidekiq.logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
6
+ logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
7
7
  end
8
8
 
9
9
  def perform(*)
@@ -4,7 +4,7 @@ class UniqueJobWithFilterMethod
4
4
  unique: :while_executing, unique_args: :filtered_args
5
5
 
6
6
  sidekiq_retries_exhausted do |msg|
7
- Sidekiq.logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
7
+ logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
8
8
  end
9
9
 
10
10
  def perform(*)
@@ -4,7 +4,7 @@ class UniqueOnAllQueuesJob
4
4
  unique: :until_executed, unique_on_all_queues: true
5
5
 
6
6
  sidekiq_retries_exhausted do |msg|
7
- Sidekiq.logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
7
+ logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
8
8
  end
9
9
 
10
10
  def perform(*)
@@ -4,7 +4,7 @@ class UntilExecutedJob
4
4
  sidekiq_options unique: :until_executed
5
5
 
6
6
  sidekiq_retries_exhausted do |msg|
7
- Sidekiq.logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
7
+ logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
8
8
  end
9
9
 
10
10
  def perform(*)
@@ -3,7 +3,7 @@ class WhileExecutingJob
3
3
  sidekiq_options queue: :working, retry: 1, backtrace: 10, unique: :while_executing
4
4
 
5
5
  sidekiq_retries_exhausted do |msg|
6
- Sidekiq.logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
6
+ logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
7
7
  end
8
8
 
9
9
  def perform(_)
@@ -0,0 +1,183 @@
1
+ require 'spec_helper'
2
+ require 'thor/runner'
3
+
4
+ RSpec.describe SidekiqUniqueJobs::Cli, ruby_ver: '>= 2.4' do
5
+ describe '#help' do
6
+ let(:output) { capture(:stdout) { described_class.start(%w[help]) } }
7
+ let(:banner) do
8
+ <<~EOS.freeze
9
+ Commands:
10
+ jobs console # drop into a console with easy access to helper methods
11
+ jobs del PATTERN # deletes unique keys from redis by pattern
12
+ jobs expire # removes all expired unique keys from the hash in redis
13
+ jobs help [COMMAND] # Describe available commands or one specific command
14
+ jobs keys PATTERN # list all unique keys and their expiry time
15
+ EOS
16
+ end
17
+
18
+ it 'displays help' do
19
+ expect(output).to include(banner)
20
+ end
21
+
22
+ describe '#help del' do
23
+ let(:output) { capture(:stdout) { described_class.start(%w[help del]) } }
24
+ let(:banner) do
25
+ <<~EOS.freeze
26
+ Usage:
27
+ jobs del PATTERN
28
+
29
+ Options:
30
+ d, [--dry-run], [--no-dry-run] # set to false to perform deletion
31
+ c, [--count=N] # The max number of keys to return
32
+ # Default: 1000
33
+
34
+ deletes unique keys from redis by pattern
35
+ EOS
36
+ end
37
+
38
+ it 'displays help about the `del` command' do
39
+ expect(output).to eq(banner)
40
+ end
41
+ end
42
+
43
+ describe '#help expire' do
44
+ let(:output) { capture(:stdout) { described_class.start(%w[help expire]) } }
45
+
46
+ let(:banner) do
47
+ <<~EOS.freeze
48
+ Usage:
49
+ jobs expire
50
+
51
+ removes all expired unique keys from the hash in redis
52
+ EOS
53
+ end
54
+
55
+ it 'displays help about the `expire` command' do
56
+ expect(output).to eq(banner)
57
+ end
58
+ end
59
+
60
+ describe '#help keys' do
61
+ let(:output) { capture(:stdout) { described_class.start(%w[help keys]) } }
62
+ let(:banner) do
63
+ <<~EOS.freeze
64
+ Usage:
65
+ jobs keys PATTERN
66
+
67
+ Options:
68
+ c, [--count=N] # The max number of keys to return
69
+ # Default: 1000
70
+
71
+ list all unique keys and their expiry time
72
+ EOS
73
+ end
74
+
75
+ it 'displays help about the `key` command' do
76
+ expect(output).to eq(banner)
77
+ end
78
+ end
79
+ end
80
+
81
+ let(:pattern) { '*' }
82
+ let(:max_lock_time) { 1 }
83
+ let(:unique_key) { 'uniquejobs:abcdefab' }
84
+ let(:jid) { 'abcdefab' }
85
+
86
+ describe '.keys' do
87
+ let(:output) { capture(:stdout) { described_class.start(%w[keys * --count 1000]) } }
88
+
89
+ context 'when no keys exist' do
90
+ let(:expected) { "Found 0 keys matching '#{pattern}':\n" }
91
+ specify { expect(output).to eq(expected) }
92
+ end
93
+
94
+ context 'when a key exists' do
95
+ let(:keys) { ['defghayl', jid, 'poilkij'].sort }
96
+ before do
97
+ keys.each do |jid|
98
+ unique_key = "uniquejobs:#{jid}"
99
+ SidekiqUniqueJobs::Scripts::AcquireLock.execute(nil, unique_key, jid, 20)
100
+ expect(SidekiqUniqueJobs)
101
+ .to have_key(unique_key)
102
+ .for_seconds(20)
103
+ .with_value(jid)
104
+ end
105
+ end
106
+
107
+ after { SidekiqUniqueJobs::Util.del('*', 1000, false) }
108
+
109
+ let(:expected) do
110
+ <<~EOS
111
+ Found 3 keys matching '#{pattern}':
112
+ uniquejobs:abcdefab uniquejobs:defghayl uniquejobs:poilkij
113
+ EOS
114
+ end
115
+ specify do
116
+ expect(output).to eq(expected)
117
+ end
118
+ end
119
+ end
120
+
121
+ describe '.del' do
122
+ let(:expected) do
123
+ <<~EOS
124
+ Deleted 1 keys matching '*'
125
+ EOS
126
+ end
127
+
128
+ before do
129
+ SidekiqUniqueJobs::Scripts::AcquireLock.execute(nil, unique_key, jid, max_lock_time)
130
+ expect(SidekiqUniqueJobs)
131
+ .to have_key(unique_key)
132
+ .for_seconds(1)
133
+ .with_value(jid)
134
+ end
135
+
136
+ specify do
137
+ output = capture(:stdout) { described_class.start(%w[del * --no-dry-run --count 1000]) }
138
+ expect(output).to eq(expected)
139
+ expect(SidekiqUniqueJobs).not_to have_key(unique_key)
140
+ end
141
+ end
142
+
143
+ describe '.expire' do
144
+ before do
145
+ SidekiqUniqueJobs::Scripts::AcquireLock.execute(nil, unique_key, jid, max_lock_time)
146
+ expect(SidekiqUniqueJobs)
147
+ .to have_key(unique_key)
148
+ .for_seconds(1)
149
+ .with_value(jid)
150
+ end
151
+ let(:expected) do
152
+ <<~EOS
153
+ Removed 1 left overs from redis.
154
+ uniquejobs:abcdefab
155
+ EOS
156
+ end
157
+
158
+ specify do
159
+ sleep 1
160
+ output = capture(:stdout) { described_class.start(%w[expire]) }
161
+ expect(output).to eq(expected)
162
+ end
163
+ end
164
+
165
+ describe '.console' do
166
+ let(:expected) do
167
+ <<~EOS
168
+ Use `keys '*', 1000 to display the first 1000 unique keys matching '*'
169
+ Use `del '*', 1000, true (default) to see how many keys would be deleted for the pattern '*'
170
+ Use `del '*', 1000, false to delete the first 1000 keys matching '*'
171
+ EOS
172
+ end
173
+ let(:output) { capture(:stdout) { described_class.start(%w[console]) } }
174
+
175
+ specify do
176
+ expect(Object).to receive(:include).with(SidekiqUniqueJobs::Util).and_return(true)
177
+ console = double(:console)
178
+ allow_any_instance_of(SidekiqUniqueJobs::Cli).to receive(:console_class).and_return(console)
179
+ allow(console).to receive(:start)
180
+ expect(output).to eq(expected)
181
+ end
182
+ end
183
+ end
@@ -9,14 +9,6 @@ RSpec.describe SidekiqUniqueJobs::Client::Middleware do
9
9
  end
10
10
 
11
11
  describe 'with real redis' do
12
- before do
13
- if defined?(Sidekiq::Extensions) && Sidekiq::Extensions.respond_to?(:enable_delay!)
14
- Sidekiq::Extensions.enable_delay!
15
- end
16
- Sidekiq.redis = REDIS
17
- Sidekiq.redis(&:flushdb)
18
- end
19
-
20
12
  describe 'when a job is already scheduled' do
21
13
  it 'processes jobs properly' do
22
14
  Sidekiq::Testing.disable! do
@@ -288,7 +280,7 @@ RSpec.describe SidekiqUniqueJobs::Client::Middleware do
288
280
  end
289
281
 
290
282
  it 'does not log duplicate payload when config turned off' do
291
- expect(Sidekiq.logger).to_not receive(:warn).with(/^payload is not unique/)
283
+ expect(SidekiqUniqueJobs.logger).to_not receive(:warn).with(/^payload is not unique/)
292
284
 
293
285
  UntilExecutedJob.sidekiq_options log_duplicate_payload: false
294
286
 
@@ -15,8 +15,6 @@ RSpec.describe SidekiqUniqueJobs::Lock::UntilAndWhileExecuting do
15
15
 
16
16
  describe '#execute' do
17
17
  before do
18
- Sidekiq.redis(&:flushdb)
19
- Sidekiq::Worker.clear_all
20
18
  subject.lock(:client)
21
19
  end
22
20
  let(:runtime_lock) { SidekiqUniqueJobs::Lock::WhileExecuting.new(item, nil) }
@@ -14,7 +14,6 @@ RSpec.describe SidekiqUniqueJobs::ScriptMock, ruby_ver: '>= 2.4.1' do
14
14
  ANOTHER_JID ||= 'anotherjid'.freeze
15
15
 
16
16
  before do
17
- Sidekiq.redis(&:flushdb)
18
17
  SidekiqUniqueJobs.configure do |config|
19
18
  config.redis_test_mode = :mock
20
19
  end
@@ -36,7 +35,6 @@ RSpec.describe SidekiqUniqueJobs::ScriptMock, ruby_ver: '>= 2.4.1' do
36
35
  SidekiqUniqueJobs.configure do |config|
37
36
  config.redis_test_mode = :redis
38
37
  end
39
- Sidekiq.redis(&:flushdb)
40
38
  end
41
39
 
42
40
  subject { SidekiqUniqueJobs::Scripts }
@@ -54,7 +52,7 @@ RSpec.describe SidekiqUniqueJobs::ScriptMock, ruby_ver: '>= 2.4.1' do
54
52
  specify { expect(lock_for).to eq(1) }
55
53
  specify do
56
54
  expect(lock_for(1)).to eq(1)
57
- expect(Redis)
55
+ expect(SidekiqUniqueJobs)
58
56
  .to have_key(UNIQUE_KEY)
59
57
  .for_seconds(1)
60
58
  .with_value('fuckit')
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe SidekiqUniqueJobs::Scripts::AcquireLock do
4
+ let(:redis_pool) { nil }
5
+ let(:jid) { 'abcdefab' }
6
+ let(:unique_key) { 'uniquejobs:123asdasd2134' }
7
+ let(:max_lock_time) { 1 }
8
+
9
+ describe '.execute' do
10
+ subject { instance_double(described_class) }
11
+
12
+ it 'delegates to instance' do
13
+ expect(described_class).to receive(:new)
14
+ .with(redis_pool, unique_key, jid, max_lock_time)
15
+ .and_return(subject)
16
+ expect(subject).to receive(:execute).and_return(true)
17
+
18
+ described_class.execute(redis_pool, unique_key, jid, max_lock_time)
19
+ end
20
+ end
21
+
22
+ describe '#execute' do
23
+ context 'when job is unique' do
24
+ def execute(myjid = jid, key = unique_key, max_lock = max_lock_time)
25
+ described_class.execute(
26
+ redis_pool,
27
+ key,
28
+ myjid,
29
+ max_lock
30
+ )
31
+ end
32
+
33
+ specify { expect(execute(jid, unique_key, max_lock_time)).to eq(true) }
34
+ specify do
35
+ expect(execute(jid, unique_key, max_lock_time)).to eq(true)
36
+ expect(SidekiqUniqueJobs)
37
+ .to have_key(unique_key)
38
+ .for_seconds(max_lock_time)
39
+ .with_value('abcdefab')
40
+ sleep 0.5
41
+ expect(execute).to eq(true)
42
+ end
43
+
44
+ context 'when a unique_key exists for another jid' do
45
+ before { expect(execute(jid, unique_key, 10)).to eq(true) }
46
+ specify { expect(execute('anotherjid', unique_key, 5)).to eq(false) }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe SidekiqUniqueJobs::Scripts::ReleaseLock do
4
+ let(:redis_pool) { nil }
5
+ let(:jid) { 'abcdefab' }
6
+ let(:unique_key) { 'uniquejobs:123asdasd2134' }
7
+ let(:max_lock_time) { 1 }
8
+
9
+ describe '.execute' do
10
+ subject { instance_double(described_class) }
11
+
12
+ it 'delegates to instance' do
13
+ expect(described_class).to receive(:new)
14
+ .with(redis_pool, unique_key, jid)
15
+ .and_return(subject)
16
+ expect(subject).to receive(:execute).and_return(true)
17
+
18
+ described_class.execute(redis_pool, unique_key, jid)
19
+ end
20
+ end
21
+
22
+ describe '#execute' do
23
+ context 'when exists' do
24
+ subject { described_class.execute(redis_pool, unique_key, jid) }
25
+
26
+ before do
27
+ SidekiqUniqueJobs::Scripts::AcquireLock.execute(redis_pool, unique_key, jid, max_lock_time)
28
+ end
29
+
30
+ specify do
31
+ expect(SidekiqUniqueJobs)
32
+ .to have_key(unique_key)
33
+ .for_seconds(max_lock_time)
34
+ .with_value(jid)
35
+
36
+ expect(subject).to eq(true)
37
+ expect(SidekiqUniqueJobs).not_to have_key(unique_key)
38
+ end
39
+ end
40
+ end
41
+ end