sidekiq-unique-jobs 5.0.0 → 5.0.1

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.

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