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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 133211c0920216c3537ec4417d74252f4804420b
4
- data.tar.gz: 3b2f8878d4ea75aaacaa9e1c82876705aefad514
3
+ metadata.gz: 54089588f5279692be7686eca3e9b4987c9cb706
4
+ data.tar.gz: 2ce8320f727817419868df6ddc877d143c9318cf
5
5
  SHA512:
6
- metadata.gz: aca4b5c230b92c0bd7b180e3738bb930e82309743008b2c17726012d851623f5a2da156141872980942a22c84e8dfefe0affe77a212d4a210d6bc8d072f47af3
7
- data.tar.gz: cfaaa3845e5bc4012df8001bb23f01f352fe99ece7e8330020b7dbf19c3e126ca76a306584dc50a76b909d279ce701b32b51ba0ca7e9b762123ea909d2924672
6
+ metadata.gz: fe2abbb06c27ef94b8266d1bcc1727b1385e7d6897c35731986d90306ce218b16b7d22336facbfff95b43e294abf5dbe4b894ce4d6629d4373e272cb20285502
7
+ data.tar.gz: eba5e89015ec74852a4a6e0615e3793ede42dee32a22e59ccd019de3a22e14b27ea27dbb757c5a4cf6cc3eab2bf80d2a6801928bb8a4030c6c3dfb45e31df593
@@ -1,36 +1,7 @@
1
1
  ---
2
- engines:
3
- csslint:
4
- enabled: true
5
- duplication:
6
- enabled: true
7
- config:
8
- languages:
9
- - ruby
10
- - javascript
11
- - python
12
- - php
13
- eslint:
14
- enabled: true
15
- fixme:
16
- enabled: true
17
- rubocop:
18
- enabled: true
19
- ratings:
20
- paths:
21
- - "**.css"
22
- - "**.inc"
23
- - "**.js"
24
- - "**.jsx"
25
- - "**.module"
26
- - "**.php"
27
- - "**.py"
28
- - "**.rb"
29
- exclude_paths:
30
- - spec/
31
2
  engines:
32
3
  bundler-audit:
33
- enabled: true
4
+ enabled: false
34
5
  fixme:
35
6
  enabled: true
36
7
  duplication:
@@ -47,7 +18,7 @@ ratings:
47
18
  - "**.rb"
48
19
  exclude_paths:
49
20
  - Gemfile
50
- - *.gemspec
21
+ - "*.gemspec"
51
22
  - Appraisals
52
23
  - spec/**/*.rb
53
24
  - gemfiles/**/*
@@ -10,5 +10,5 @@ insert_final_newline = true
10
10
  indent_style = space
11
11
  indent_size = 2
12
12
 
13
- [*.md]
14
- trim_trailing_whitespace = true
13
+ [*.{md,slim,haml}]
14
+ trim_trailing_whitespace = false
@@ -22,7 +22,7 @@ Metrics/CyclomaticComplexity:
22
22
  Max: 7
23
23
 
24
24
  Metrics/LineLength:
25
- Max: 108
25
+ Max: 120
26
26
 
27
27
  Metrics/MethodLength:
28
28
  Max: 13
@@ -1,10 +1,10 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  cache: bundler
4
- before_install:
5
- - rvm get head
6
- - gem update --system
7
- - gem install bundler
4
+ # before_install:
5
+ # - rvm get head
6
+ # - gem update --system
7
+ # - gem install bundler
8
8
  services:
9
9
  - redis-server
10
10
  script:
@@ -1,6 +1,13 @@
1
+ ## v5.0.1
2
+
3
+ - Added a command line util for cleaning out expired keys
4
+ - Use `SidekiqUniqueJobs.logger` instead of spreading out `Sidekiq.logger` everywhere.
5
+
1
6
  ## v5.0.0
2
7
 
3
8
  - Only support Sidekiq >= 4
9
+ - Removed overrides and support for older Sidekiq testing
10
+ - Added coverage
4
11
 
5
12
  ## v4.0.18
6
13
 
data/README.md CHANGED
@@ -5,15 +5,11 @@ The missing unique jobs for sidekiq
5
5
  ## Requirements
6
6
 
7
7
 
8
- See https://github.com/mperham/sidekiq#requirements for what is required. Starting from 3.0.13 only sidekiq 3 is supported and support for MRI 1.9 is dropped (it might work but won't be worked on)
8
+ See https://github.com/mperham/sidekiq#requirements for what is required. Starting from 5.0.0 only sidekiq >= 4 is supported and support for MRI <= 2.1 is dropped.
9
9
 
10
10
  ### Version 4 Upgrade instructions
11
11
 
12
- Version 4 requires redis >= 2.6.2!! Don't upgrade to version 4 unless you are on redis >= 2.6.2.
13
-
14
- Easy path - Drop all your unique jobs before upgrading the gem!
15
-
16
- Hard path - See below... Start with a clean slate :)
12
+ Version 5 requires redis >= 3
17
13
 
18
14
  ## Installation
19
15
 
@@ -26,10 +26,14 @@ module SidekiqUniqueJobs
26
26
  default_queue_lock_expiration: 30 * 60,
27
27
  default_run_lock_expiration: 60,
28
28
  default_lock: :while_executing,
29
- redis_test_mode: :redis # :mock
29
+ redis_test_mode: :redis, # :mock
30
30
  )
31
31
  end
32
32
 
33
+ def logger
34
+ Sidekiq.logger
35
+ end
36
+
33
37
  def default_lock
34
38
  config.default_lock
35
39
  end
@@ -3,6 +3,9 @@ require 'timeout'
3
3
 
4
4
  module Sidekiq
5
5
  class Simulator
6
+ extend Forwardable
7
+ def_delegator SidekiqUniqueJobs, :logger
8
+
6
9
  attr_reader :queues, :launcher
7
10
 
8
11
  def self.process_queue(queue)
@@ -67,9 +70,5 @@ module Sidekiq
67
70
  verbose: false,
68
71
  logfile: './tmp/sidekiq.log' }
69
72
  end
70
-
71
- def logger
72
- @logger ||= Sidekiq.logger
73
- end
74
73
  end
75
74
  end
@@ -2,30 +2,52 @@ require 'thor'
2
2
 
3
3
  module SidekiqUniqueJobs
4
4
  class Cli < Thor
5
+ # def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel)
6
+ # @argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
7
+ # end
8
+
9
+ def self.banner(command, _namespace = nil, _subcommand = false)
10
+ "jobs #{@package_name} #{command.usage}"
11
+ end
12
+
5
13
  desc 'keys PATTERN', 'list all unique keys and their expiry time'
6
14
  option :count, aliases: :c, type: :numeric, default: 1000, desc: 'The max number of keys to return'
7
15
  def keys(pattern)
8
- Util.keys(pattern, options[:count])
16
+ keys = Util.keys(pattern, options[:count])
17
+ say "Found #{keys.size} keys matching '#{pattern}':"
18
+ print_in_columns(keys.sort) if keys.any?
9
19
  end
10
20
 
11
21
  desc 'del PATTERN', 'deletes unique keys from redis by pattern'
12
22
  option :dry_run, aliases: :d, type: :boolean, desc: 'set to false to perform deletion'
13
23
  option :count, aliases: :c, type: :numeric, default: 1000, desc: 'The max number of keys to return'
14
24
  def del(pattern)
15
- Util.del(pattern, options[:count], options[:dry_run])
25
+ deleted_count = Util.del(pattern, options[:count], options[:dry_run])
26
+ say "Deleted #{deleted_count} keys matching '#{pattern}'"
27
+ end
28
+
29
+ desc 'expire', 'removes all expired unique keys from the hash in redis'
30
+ def expire
31
+ expired = Util.expire
32
+ say "Removed #{expired.values.size} left overs from redis."
33
+ print_in_columns(expired.values)
16
34
  end
17
35
 
18
36
  desc 'console', 'drop into a console with easy access to helper methods'
19
37
  def console
20
- puts "Use `keys '*', 1000 to display the first 1000 unique keys matching '*'"
21
- puts "Use `del '*', 1000, true (default) to see how many keys would be deleted for the pattern '*'"
22
- puts "Use `del '*', 1000, false to delete the first 1000 keys matching '*'"
38
+ say "Use `keys '*', 1000 to display the first 1000 unique keys matching '*'"
39
+ say "Use `del '*', 1000, true (default) to see how many keys would be deleted for the pattern '*'"
40
+ say "Use `del '*', 1000, false to delete the first 1000 keys matching '*'"
23
41
  Object.include SidekiqUniqueJobs::Util
24
42
  console_class.start
25
43
  end
26
44
 
27
45
  private
28
46
 
47
+ def logger
48
+ SidekiqUniqueJobs.logger
49
+ end
50
+
29
51
  def console_class
30
52
  require 'pry'
31
53
  Pry
@@ -35,25 +35,17 @@ module SidekiqUniqueJobs
35
35
  unlock_by_key(unique_key, item[JID_KEY], redis_pool)
36
36
  end
37
37
 
38
- # rubocop:disable MethodLength
39
38
  def lock(scope)
40
39
  if scope.to_sym != :client
41
40
  raise ArgumentError, "#{scope} middleware can't #{__method__} #{unique_key}"
42
41
  end
43
42
 
44
- result = Scripts.call(:acquire_lock, redis_pool,
45
- keys: [unique_key],
46
- argv: [item[JID_KEY], max_lock_time])
47
- case result
48
- when 1
49
- logger.debug { "successfully locked #{unique_key} for #{max_lock_time} seconds" }
50
- true
51
- when 0
52
- logger.debug { "failed to acquire lock for #{unique_key}" }
53
- false
54
- else
55
- raise "#{__method__} returned an unexpected value (#{result})"
56
- end
43
+ Scripts::AcquireLock.execute(
44
+ redis_pool,
45
+ unique_key,
46
+ item[JID_KEY],
47
+ max_lock_time
48
+ )
57
49
  end
58
50
  # rubocop:enable MethodLength
59
51
 
@@ -1,9 +1,15 @@
1
1
  require 'pathname'
2
2
  require 'digest/sha1'
3
3
  require 'concurrent/map'
4
+ require 'sidekiq_unique_jobs/scripts/acquire_lock'
5
+ require 'sidekiq_unique_jobs/scripts/release_lock'
4
6
 
5
7
  module SidekiqUniqueJobs
6
- ScriptError = Class.new(StandardError)
8
+ ScriptError = Class.new(StandardError)
9
+ UniqueKeyMissing = Class.new(ArgumentError)
10
+ JidMissing = Class.new(ArgumentError)
11
+ MaxLockTimeMissing = Class.new(ArgumentError)
12
+ UnexpectedValue = Class.new(StandardError)
7
13
 
8
14
  module Scripts
9
15
  LUA_PATHNAME ||= Pathname.new(__FILE__).dirname.join('../../redis').freeze
@@ -14,11 +20,7 @@ module SidekiqUniqueJobs
14
20
  module_function
15
21
 
16
22
  extend SingleForwardable
17
- def_delegator :SidekiqUniqueJobs, :connection
18
-
19
- def logger
20
- Sidekiq.logger
21
- end
23
+ def_delegators :SidekiqUniqueJobs, :connection, :logger
22
24
 
23
25
  def call(file_name, redis_pool, options = {}) # rubocop:disable MethodLength
24
26
  connection(redis_pool) do |redis|
@@ -0,0 +1,45 @@
1
+ module SidekiqUniqueJobs
2
+ module Scripts
3
+ class AcquireLock
4
+ extend Forwardable
5
+ def_delegator SidekiqUniqueJobs, :logger
6
+
7
+ def self.execute(redis_pool, unique_key, jid, max_lock_time)
8
+ new(redis_pool, unique_key, jid, max_lock_time).execute
9
+ end
10
+
11
+ attr_reader :redis_pool, :unique_key, :jid, :max_lock_time
12
+
13
+ def initialize(_redis_pool, unique_key, jid, max_lock_time)
14
+ raise UniqueKeyMissing, 'unique_key is required' if unique_key.nil?
15
+ raise JidMissing, 'jid is required' if jid.nil?
16
+ raise MaxLockTimeMissing, 'max_lock_time is required' if max_lock_time.nil?
17
+
18
+ @unique_key = unique_key
19
+ @jid = jid
20
+ @max_lock_time = max_lock_time
21
+ end
22
+
23
+ def execute
24
+ result = Scripts.call(:acquire_lock, redis_pool,
25
+ keys: [unique_key],
26
+ argv: [jid, max_lock_time])
27
+
28
+ handle_result(result)
29
+ end
30
+
31
+ def handle_result(result)
32
+ case result
33
+ when 1
34
+ logger.debug { "successfully acquired lock #{unique_key} for #{max_lock_time} seconds" }
35
+ true
36
+ when 0
37
+ logger.debug { "failed to acquire lock for #{unique_key}" }
38
+ false
39
+ else
40
+ raise UnexpectedValue, "failed to acquire lock : unexpected return value (#{result})"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,47 @@
1
+ module SidekiqUniqueJobs
2
+ module Scripts
3
+ class ReleaseLock
4
+ extend Forwardable
5
+ def_delegator SidekiqUniqueJobs, :logger
6
+
7
+ def self.execute(redis_pool, unique_key, jid)
8
+ new(redis_pool, unique_key, jid).execute
9
+ end
10
+
11
+ attr_reader :redis_pool, :unique_key, :jid
12
+
13
+ def initialize(redis_pool, unique_key, jid)
14
+ raise UniqueKeyMissing, 'unique_key is required' if unique_key.nil?
15
+ raise JidMissing, 'jid is required' if jid.nil?
16
+
17
+ @redis_pool = redis_pool
18
+ @unique_key = unique_key
19
+ @jid = jid
20
+ end
21
+
22
+ def execute
23
+ result = Scripts.call(:release_lock, redis_pool,
24
+ keys: [unique_key],
25
+ argv: [jid])
26
+
27
+ handle_result(result)
28
+ end
29
+
30
+ def handle_result(result)
31
+ case result
32
+ when 1
33
+ logger.debug { "successfully unlocked #{unique_key}" }
34
+ true
35
+ when 0
36
+ logger.debug { "expiring lock #{unique_key} is not owned by #{jid}" }
37
+ false
38
+ when -1
39
+ logger.debug { "#{unique_key} is not a known key" }
40
+ false
41
+ else
42
+ raise UnexpectedValue, "failed to release lock : unexpected return value (#{result})"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -25,7 +25,7 @@ module SidekiqUniqueJobs
25
25
 
26
26
  module Testing
27
27
  def call_ext(file_name, redis_pool, options = {})
28
- if SidekiqUniqueJobs.config.redis_test_mode == :mock
28
+ if SidekiqUniqueJobs.mocked?
29
29
  SidekiqUniqueJobs::ScriptMock.call(file_name, redis_pool, options)
30
30
  else
31
31
  call_orig(file_name, redis_pool, options)
@@ -19,10 +19,10 @@ module SidekiqUniqueJobs
19
19
  def initialize(job)
20
20
  Sidekiq::Logging.with_context(CLASS_NAME) do
21
21
  @item = job
22
- @worker_class ||= worker_class_constantize(@item[CLASS_KEY])
22
+ @worker_class ||= worker_class_constantize(@item[CLASS_KEY])
23
23
  @item[UNIQUE_PREFIX_KEY] ||= unique_prefix
24
- @item[UNIQUE_ARGS_KEY] = unique_args(@item[ARGS_KEY]) # SIC! Calculate unique_args unconditionally
25
- @item[UNIQUE_DIGEST_KEY] = unique_digest
24
+ @item[UNIQUE_ARGS_KEY] = unique_args(@item[ARGS_KEY])
25
+ @item[UNIQUE_DIGEST_KEY] = unique_digest
26
26
  end
27
27
  end
28
28
 
@@ -7,26 +7,12 @@ module SidekiqUniqueJobs
7
7
  end
8
8
 
9
9
  def unlock_by_key(unique_key, jid, redis_pool = nil)
10
- result = Scripts.call(:release_lock, redis_pool, keys: [unique_key], argv: [jid])
11
- after_unlock(result, __method__, unique_key, jid)
10
+ return false unless Scripts::ReleaseLock.execute(redis_pool, unique_key, jid)
11
+ after_unlock(jid)
12
12
  end
13
13
 
14
- def after_unlock(result, calling_method, unique_key, jid) # rubocop:disable Metrics/MethodLength
14
+ def after_unlock(jid)
15
15
  ensure_job_id_removed(jid)
16
-
17
- case result
18
- when 1
19
- logger.debug { "successfully unlocked #{unique_key}" }
20
- true
21
- when 0
22
- logger.debug { "expiring lock #{unique_key} is not owned by #{jid}" }
23
- false
24
- when -1
25
- logger.debug { "#{unique_key} is not a known key" }
26
- false
27
- else
28
- raise "#{calling_method} returned an unexpected value (#{result})"
29
- end
30
16
  end
31
17
 
32
18
  def ensure_job_id_removed(jid)
@@ -34,7 +20,7 @@ module SidekiqUniqueJobs
34
20
  end
35
21
 
36
22
  def logger
37
- Sidekiq.logger
23
+ SidekiqUniqueJobs.logger
38
24
  end
39
25
  end
40
26
  end
@@ -18,7 +18,7 @@ module SidekiqUniqueJobs
18
18
  end
19
19
 
20
20
  def del(pattern = SCAN_PATTERN, count = 0, dry_run = true)
21
- raise 'Please provide a number of keys to delete greater than zero' if count.zero?
21
+ raise ArgumentError, 'Please provide a number of keys to delete greater than zero' if count.zero?
22
22
  logger.debug { "Deleting keys by: #{pattern}" }
23
23
  keys, time = timed { keys(pattern, count) }
24
24
  logger.debug { "#{keys.size} matching keys found in #{time} sec." }
@@ -32,6 +32,24 @@ module SidekiqUniqueJobs
32
32
  keys.size
33
33
  end
34
34
 
35
+ def unique_hash
36
+ connection do |conn|
37
+ conn.hgetall(SidekiqUniqueJobs::HASH_KEY)
38
+ end
39
+ end
40
+
41
+ def expire
42
+ removed_keys = {}
43
+ connection do |conn|
44
+ conn.hgetall(SidekiqUniqueJobs::HASH_KEY).each do |jid, unique_key|
45
+ next if conn.get(unique_key)
46
+ conn.hdel(SidekiqUniqueJobs::HASH_KEY, jid)
47
+ removed_keys[jid] = unique_key
48
+ end
49
+ end
50
+ removed_keys
51
+ end
52
+
35
53
  def keys_by_scan(pattern, count)
36
54
  connection { |conn| conn.scan_each(match: prefix(pattern), count: count).to_a }
37
55
  end
@@ -72,6 +90,7 @@ module SidekiqUniqueJobs
72
90
 
73
91
  def prefix(key)
74
92
  return key if unique_prefix.nil?
93
+ return key if key.start_with?("#{unique_prefix}:")
75
94
  "#{unique_prefix}:#{key}"
76
95
  end
77
96
 
@@ -92,7 +111,7 @@ module SidekiqUniqueJobs
92
111
  end
93
112
 
94
113
  def logger
95
- Sidekiq.logger
114
+ SidekiqUniqueJobs.logger
96
115
  end
97
116
  end
98
117
  end