sidekiq-unique-jobs 4.0.0 → 4.0.7

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +1 -1
  3. data/.travis.yml +1 -2
  4. data/Appraisals +1 -1
  5. data/CHANGELOG.md +44 -1
  6. data/Gemfile +1 -2
  7. data/README.md +49 -51
  8. data/circle.yml +3 -1
  9. data/gemfiles/sidekiq_2.17.gemfile +7 -5
  10. data/gemfiles/sidekiq_3.0.gemfile +6 -4
  11. data/gemfiles/sidekiq_3.1.gemfile +6 -4
  12. data/gemfiles/sidekiq_3.2.gemfile +6 -4
  13. data/gemfiles/sidekiq_3.3.gemfile +6 -4
  14. data/gemfiles/sidekiq_develop.gemfile +6 -4
  15. data/lib/sidekiq-unique-jobs.rb +3 -6
  16. data/lib/sidekiq_unique_jobs/config.rb +1 -6
  17. data/lib/sidekiq_unique_jobs/constants.rb +17 -0
  18. data/lib/sidekiq_unique_jobs/core_ext.rb +28 -23
  19. data/lib/sidekiq_unique_jobs/lock.rb +1 -1
  20. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +13 -0
  21. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +17 -3
  22. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +4 -0
  23. data/lib/sidekiq_unique_jobs/lock/until_timeout.rb +9 -2
  24. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +17 -5
  25. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +14 -5
  26. data/lib/sidekiq_unique_jobs/server/middleware.rb +3 -40
  27. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +29 -2
  28. data/lib/sidekiq_unique_jobs/testing/sidekiq_overrides.rb +6 -2
  29. data/lib/sidekiq_unique_jobs/timeout_calculator.rb +42 -0
  30. data/lib/sidekiq_unique_jobs/unique_args.rb +31 -29
  31. data/lib/sidekiq_unique_jobs/version.rb +1 -1
  32. data/redis/synchronize.lua +1 -1
  33. data/sidekiq-unique-jobs.gemspec +1 -2
  34. data/spec/{workers/unique_worker.rb → jobs/another_unique_job.rb} +3 -3
  35. data/spec/{workers/queue_worker.rb → jobs/custom_queue_job.rb} +1 -1
  36. data/spec/jobs/custom_queue_job_with_filter_method.rb +7 -0
  37. data/spec/{workers/queue_worker_with_filter_proc.rb → jobs/custom_queue_job_with_filter_proc.rb} +2 -3
  38. data/spec/jobs/expiring_job.rb +4 -0
  39. data/spec/{workers → jobs}/inline_worker.rb +1 -1
  40. data/spec/jobs/just_a_worker.rb +8 -0
  41. data/spec/jobs/main_job.rb +8 -0
  42. data/spec/{workers/my_worker.rb → jobs/my_job.rb} +2 -3
  43. data/spec/jobs/my_unique_job.rb +7 -0
  44. data/spec/{workers → jobs}/plain_class.rb +0 -0
  45. data/spec/{workers → jobs}/test_class.rb +0 -0
  46. data/spec/{workers → jobs}/unique_job_with_filter_method.rb +2 -2
  47. data/spec/{workers/unique_on_all_queues_worker.rb → jobs/unique_on_all_queues_job.rb} +3 -3
  48. data/spec/jobs/until_and_while_executing_job.rb +8 -0
  49. data/spec/{workers/another_unique_worker.rb → jobs/until_executed_job.rb} +6 -2
  50. data/spec/jobs/until_executing_job.rb +8 -0
  51. data/spec/jobs/until_global_timeout_job.rb +8 -0
  52. data/spec/{workers/inline_expiration_worker.rb → jobs/until_timeout_job.rb} +2 -2
  53. data/spec/{workers/after_unlock_worker.rb → jobs/while_executing_job.rb} +2 -3
  54. data/spec/lib/sidekiq_unique_jobs/client/middleware_spec.rb +60 -44
  55. data/spec/lib/sidekiq_unique_jobs/lock/until_and_while_executing_spec.rb +39 -0
  56. data/spec/lib/sidekiq_unique_jobs/lock/until_executed_spec.rb +40 -0
  57. data/spec/lib/sidekiq_unique_jobs/lock/while_executing_spec.rb +15 -4
  58. data/spec/lib/sidekiq_unique_jobs/options_with_fallback_spec.rb +25 -0
  59. data/spec/lib/sidekiq_unique_jobs/scripts_spec.rb +3 -4
  60. data/spec/lib/sidekiq_unique_jobs/server/middleware_spec.rb +51 -68
  61. data/spec/lib/sidekiq_unique_jobs/sidekiq_testing_enabled_spec.rb +75 -79
  62. data/spec/lib/sidekiq_unique_jobs/sidekiq_unique_ext_spec.rb +2 -2
  63. data/spec/lib/sidekiq_unique_jobs/{lock/time_calculator_spec.rb → timeout_calculator_spec.rb} +10 -11
  64. data/spec/lib/sidekiq_unique_jobs/unique_args_spec.rb +28 -45
  65. data/spec/spec_helper.rb +23 -15
  66. data/spec/support/unique_macros.rb +20 -1
  67. metadata +30 -42
  68. data/lib/sidekiq_unique_jobs/lock/time_calculator.rb +0 -44
  69. data/spec/workers/after_yield_worker.rb +0 -17
  70. data/spec/workers/before_yield_worker.rb +0 -9
  71. data/spec/workers/expiring_worker.rb +0 -4
  72. data/spec/workers/inline_unlock_order_worker.rb +0 -8
  73. data/spec/workers/just_a_worker.rb +0 -8
  74. data/spec/workers/main_job.rb +0 -8
  75. data/spec/workers/my_unique_worker.rb +0 -8
  76. data/spec/workers/queue_worker_with_filter_method.rb +0 -7
  77. data/spec/workers/run_lock_with_retries_worker.rb +0 -12
  78. data/spec/workers/run_lock_worker.rb +0 -7
  79. data/spec/workers/while_executing_worker.rb +0 -13
@@ -3,13 +3,15 @@
3
3
  source "http://rubygems.org"
4
4
 
5
5
  gem "appraisal", "~> 2.0.0"
6
+ gem "rspec-its", :require => false
6
7
  gem "sidekiq", "~> 3.3.0"
7
8
 
8
- platforms :mri do
9
- gem "pry-suite", :require => false
10
- gem "let_it_go", :require => false
11
- gem "memory-profiler", :require => false
9
+ platforms :mri_22 do
10
+ gem "pry", :require => false
11
+ gem "pry-rescue", :require => false
12
+ gem "pry-byebug", :require => false
12
13
  gem "simplecov-json", :require => false
14
+ gem "memory_profiler", :require => false
13
15
  gem "codeclimate-test-reporter", :require => false
14
16
  end
15
17
 
@@ -3,13 +3,15 @@
3
3
  source "http://rubygems.org"
4
4
 
5
5
  gem "appraisal", "~> 2.0.0"
6
+ gem "rspec-its", :require => false
6
7
  gem "sidekiq", :github => "mperham/sidekiq"
7
8
 
8
- platforms :mri do
9
- gem "pry-suite", :require => false
10
- gem "let_it_go", :require => false
11
- gem "memory-profiler", :require => false
9
+ platforms :mri_22 do
10
+ gem "pry", :require => false
11
+ gem "pry-rescue", :require => false
12
+ gem "pry-byebug", :require => false
12
13
  gem "simplecov-json", :require => false
14
+ gem "memory_profiler", :require => false
13
15
  gem "codeclimate-test-reporter", :require => false
14
16
  end
15
17
 
@@ -1,5 +1,7 @@
1
1
  require 'yaml' if RUBY_VERSION.include?('2.0.0') # rubocop:disable FileName
2
+ require 'sidekiq_unique_jobs/constants'
2
3
  require 'sidekiq_unique_jobs/core_ext'
4
+ require 'sidekiq_unique_jobs/timeout_calculator'
3
5
  require 'sidekiq_unique_jobs/options_with_fallback'
4
6
  require 'sidekiq_unique_jobs/scripts'
5
7
  require 'sidekiq_unique_jobs/unique_args'
@@ -18,17 +20,12 @@ module SidekiqUniqueJobs
18
20
  def config
19
21
  @config ||= Config.new(
20
22
  unique_prefix: 'uniquejobs',
21
- unique_args_enabled: true,
22
23
  default_expiration: 30 * 60,
23
24
  default_lock: :while_executing,
24
25
  redis_test_mode: :redis # :mock
25
26
  )
26
27
  end
27
28
 
28
- def unique_args_enabled?
29
- config.unique_args_enabled
30
- end
31
-
32
29
  def default_lock
33
30
  config.default_lock
34
31
  end
@@ -38,7 +35,7 @@ module SidekiqUniqueJobs
38
35
  yield config
39
36
  else
40
37
  options.each do |key, val|
41
- config[key] = val
38
+ config.send("#{key}=", val)
42
39
  end
43
40
  end
44
41
  end
@@ -1,6 +1,5 @@
1
1
  module SidekiqUniqueJobs
2
2
  class Config < OpenStruct
3
- TESTING_CONSTANT ||= 'Testing'.freeze
4
3
  CONFIG_ACCESSORS = [
5
4
  :unique_prefix,
6
5
  :default_expiration,
@@ -17,11 +16,7 @@ module SidekiqUniqueJobs
17
16
  end
18
17
 
19
18
  def testing_enabled?
20
- Sidekiq.const_defined?(TESTING_CONSTANT) && Sidekiq::Testing.enabled?
21
- end
22
-
23
- def unique_args_enabled?
24
- config.unique_args_enabled
19
+ Sidekiq.const_defined?(TESTING_CONSTANT, false) && Sidekiq::Testing.enabled?
25
20
  end
26
21
  end
27
22
  end
@@ -0,0 +1,17 @@
1
+ module SidekiqUniqueJobs
2
+ ARGS_KEY ||= 'args'.freeze
3
+ AT_KEY ||= 'at'.freeze
4
+ CLASS_KEY ||= 'class'.freeze
5
+ JID_KEY ||= 'jid'.freeze
6
+ LOG_DUPLICATE_KEY ||= 'log_duplicate_payload'.freeze
7
+ QUEUE_KEY ||= 'queue'.freeze
8
+ TESTING_CONSTANT ||= 'Testing'.freeze
9
+ UNIQUE_KEY ||= 'unique'.freeze
10
+ UNIQUE_LOCK_KEY ||= 'unique_lock'.freeze
11
+ UNIQUE_ARGS_KEY ||= 'unique_args'.freeze
12
+ UNIQUE_PREFIX_KEY ||= 'unique_prefix'.freeze
13
+ UNIQUE_DIGEST_KEY ||= 'unique_digest'.freeze
14
+ UNIQUE_ON_ALL_QUEUES_KEY ||= 'unique_on_all_queues'.freeze
15
+ UNIQUE_TIMEOUT_KEY ||= 'unique_expiration'.freeze
16
+ UNIQUE_ARGS_ENABLED_KEY ||= 'unique_args_enabled'.freeze
17
+ end
@@ -19,28 +19,33 @@ rescue LoadError
19
19
  end unless {}.respond_to?(:slice!)
20
20
  end
21
21
  end
22
- begin
23
- require 'active_support/core_ext/string/inflections'
24
- rescue LoadError
25
- class String
26
- # File activesupport/lib/active_support/inflector/methods.rb, line 178
27
- def classify
28
- camelize(singularize(sub(/.*\./, '')))
29
- end unless ''.respond_to?(:classify)
30
22
 
31
- # File activesupport/lib/active_support/inflector/methods.rb, line 67
32
- def camelize(uppercase_first_letter = true)
33
- string = self
34
- if uppercase_first_letter
35
- string = string.sub(/^[a-z\d]*/) { $&.capitalize }
36
- else
37
- string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { $&.downcase }
38
- end
39
- string.gsub!(%r{(?:_|(\/))([a-z\d]*)}i) do
40
- "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}"
41
- end
42
- string.gsub!(%r{/}, '::')
43
- string
44
- end unless ''.respond_to?(:camelize)
45
- end
23
+ class Array
24
+ def extract_options!
25
+ if last.is_a?(Hash) && last.instance_of?(Hash)
26
+ pop
27
+ else
28
+ {}
29
+ end
30
+ end unless [].respond_to?(:extract_options!)
31
+ end
32
+
33
+ class String
34
+ def classify
35
+ camelize(sub(/.*\./, ''))
36
+ end unless ''.respond_to?(:classify)
37
+
38
+ def camelize(uppercase_first_letter = true)
39
+ string = self
40
+ if uppercase_first_letter
41
+ string = string.sub(/^[a-z\d]*/) { $&.capitalize }
42
+ else
43
+ string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { $&.downcase }
44
+ end
45
+ string.gsub!(%r{(?:_|(\/))([a-z\d]*)}i) do
46
+ "#{Regexp.last_match(1)}#{Regexp.last_match(2).capitalize}"
47
+ end
48
+ string.gsub!(%r{/}, '::')
49
+ string
50
+ end unless ''.respond_to?(:camelize)
46
51
  end
@@ -1,8 +1,8 @@
1
- require 'sidekiq_unique_jobs/lock/time_calculator'
2
1
  require 'sidekiq_unique_jobs/lock/until_executed'
3
2
  require 'sidekiq_unique_jobs/lock/until_executing'
4
3
  require 'sidekiq_unique_jobs/lock/while_executing'
5
4
  require 'sidekiq_unique_jobs/lock/until_timeout'
5
+ require 'sidekiq_unique_jobs/lock/until_and_while_executing'
6
6
 
7
7
  module SidekiqUniqueJobs
8
8
  module Lock
@@ -0,0 +1,13 @@
1
+ module SidekiqUniqueJobs
2
+ module Lock
3
+ class UntilAndWhileExecuting < UntilExecuting
4
+ def execute(callback)
5
+ lock = WhileExecuting.new(item, redis_pool)
6
+ lock.synchronize do
7
+ callback.call if unlock(:server)
8
+ yield
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -11,11 +11,21 @@ module SidekiqUniqueJobs
11
11
  @redis_pool = redis_pool
12
12
  end
13
13
 
14
+ def execute(callback, &blk)
15
+ operative = true
16
+ send(:after_yield_yield, &blk)
17
+ rescue Sidekiq::Shutdown
18
+ operative = false
19
+ raise
20
+ ensure
21
+ callback.call if operative && unlock(:server)
22
+ end
23
+
14
24
  def unlock(scope)
15
25
  unless [:server, :api, :test].include?(scope)
16
26
  fail ArgumentError, "#{scope} middleware can't #{__method__} #{unique_key}"
17
27
  end
18
- SidekiqUniqueJobs::Unlockable.unlock(unique_key, item['jid'.freeze], redis_pool)
28
+ SidekiqUniqueJobs::Unlockable.unlock(unique_key, item[JID_KEY], redis_pool)
19
29
  end
20
30
 
21
31
  # rubocop:disable MethodLength
@@ -26,7 +36,7 @@ module SidekiqUniqueJobs
26
36
 
27
37
  result = Scripts.call(:aquire_lock, redis_pool,
28
38
  keys: [unique_key],
29
- argv: [item['jid'.freeze], max_lock_time])
39
+ argv: [item[JID_KEY], max_lock_time])
30
40
  case result
31
41
  when 1
32
42
  logger.debug { "successfully locked #{unique_key} for #{max_lock_time} seconds" }
@@ -45,7 +55,11 @@ module SidekiqUniqueJobs
45
55
  end
46
56
 
47
57
  def max_lock_time
48
- @max_lock_time ||= TimeCalculator.for_item(item).seconds
58
+ @max_lock_time ||= TimeoutCalculator.for_item(item).seconds
59
+ end
60
+
61
+ def after_yield_yield
62
+ yield
49
63
  end
50
64
 
51
65
  private
@@ -1,6 +1,10 @@
1
1
  module SidekiqUniqueJobs
2
2
  module Lock
3
3
  class UntilExecuting < UntilExecuted
4
+ def execute(callback, &_block)
5
+ callback.call if unlock(:server)
6
+ yield
7
+ end
4
8
  end
5
9
  end
6
10
  end
@@ -2,8 +2,15 @@ module SidekiqUniqueJobs
2
2
  module Lock
3
3
  class UntilTimeout < UntilExecuted
4
4
  def unlock(scope)
5
- return true if scope.to_sym == :server
6
- fail ArgumentError, "#{scope} middleware can't #{__method__} #{unique_key}"
5
+ if scope.to_sym == :server
6
+ return true
7
+ else
8
+ fail ArgumentError, "#{scope} middleware can't #{__method__} #{unique_key}"
9
+ end
10
+ end
11
+
12
+ def execute(_callback)
13
+ yield if block_given?
7
14
  end
8
15
  end
9
16
  end
@@ -6,25 +6,37 @@ module SidekiqUniqueJobs
6
6
  end
7
7
 
8
8
  def initialize(item, redis_pool = nil)
9
- @unique_digest = item['unique_digest'.freeze]
10
- @run_key = "#{@unique_digest}:run"
9
+ @item = item
11
10
  @mutex = Mutex.new
12
11
  @redis_pool = redis_pool
12
+ @unique_digest = "#{create_digest}:run"
13
+ yield self if block_given?
13
14
  end
14
15
 
15
- def synchronize(&_block)
16
+ def synchronize
16
17
  @mutex.lock
17
18
  sleep 0.001 until locked?
18
19
 
19
20
  yield
20
21
 
21
22
  ensure
22
- SidekiqUniqueJobs.connection(@redis_pool) { |c| c.del @run_key }
23
+ SidekiqUniqueJobs.connection(@redis_pool) { |c| c.del @unique_digest }
23
24
  @mutex.unlock
24
25
  end
25
26
 
26
27
  def locked?
27
- Scripts.call(:synchronize, @redis_pool, keys: [@run_key], argv: [Time.now.to_i]) == 1
28
+ Scripts.call(:synchronize, @redis_pool, keys: [@unique_digest], argv: [Time.now.to_i]) == 1
29
+ end
30
+
31
+ def execute(_callback)
32
+ synchronize do
33
+ yield
34
+ end
35
+ end
36
+
37
+ def create_digest
38
+ @unique_digest ||= @item[UNIQUE_DIGEST_KEY]
39
+ @unique_digest ||= SidekiqUniqueJobs::UniqueArgs.digest(@item)
28
40
  end
29
41
  end
30
42
  end
@@ -1,8 +1,9 @@
1
1
  module SidekiqUniqueJobs
2
2
  module OptionsWithFallback
3
- UNIQUE_KEY ||= 'unique'.freeze
4
- UNIQUE_LOCK_KEY ||= 'unique_lock'.freeze
5
- LOG_DUPLICATE_KEY ||= 'log_duplicate_payload'.freeze
3
+ def self.included(base)
4
+ base.class_attribute :lock_cache unless base.respond_to?(:lock_cache)
5
+ base.lock_cache ||= {}
6
+ end
6
7
 
7
8
  def unique_enabled?
8
9
  options[UNIQUE_KEY] || item[UNIQUE_KEY]
@@ -21,16 +22,24 @@ module SidekiqUniqueJobs
21
22
  end
22
23
 
23
24
  def lock_class
24
- "SidekiqUniqueJobs::Lock::#{unique_lock.to_s.classify}".constantize
25
+ lock_cache[unique_lock.to_sym] ||= "SidekiqUniqueJobs::Lock::#{unique_lock.to_s.classify}".constantize
25
26
  end
26
27
 
27
28
  def unique_lock
28
- options[UNIQUE_LOCK_KEY] || item[UNIQUE_LOCK_KEY] || SidekiqUniqueJobs.default_lock
29
+ if options.key?(UNIQUE_KEY) && options[UNIQUE_KEY] == true
30
+ warn('unique: true is no longer valid. Please set it to the type of lock required like: ' \
31
+ '`unique: :until_executed`')
32
+ options[UNIQUE_LOCK_KEY] || SidekiqUniqueJobs.default_lock
33
+ else
34
+ options[UNIQUE_KEY] || item[UNIQUE_KEY] || SidekiqUniqueJobs.default_lock
35
+ end
29
36
  end
30
37
 
31
38
  def options
32
39
  @options ||= worker_class.get_sidekiq_options if worker_class.respond_to?(:get_sidekiq_options)
40
+ @options ||= Sidekiq.default_worker_options
33
41
  @options ||= {}
42
+ @options &&= @options.stringify_keys
34
43
  end
35
44
  end
36
45
  end
@@ -1,4 +1,3 @@
1
- require 'digest'
2
1
  require 'forwardable'
3
2
 
4
3
  module SidekiqUniqueJobs
@@ -15,54 +14,18 @@ module SidekiqUniqueJobs
15
14
  @redis_pool = redis_pool
16
15
  @queue = queue
17
16
  @item = item
18
-
19
- send(unique_lock, &blk)
17
+ return yield unless unique_enabled?
18
+ lock.send(:execute, after_unlock_hook, &blk)
20
19
  end
21
20
 
22
21
  private
23
22
 
24
23
  attr_reader :redis_pool, :worker, :item, :worker_class
25
24
 
26
- def until_executing
27
- unlock
28
- yield
29
- end
30
-
31
- def until_executed(&block)
32
- operative = true
33
- after_yield_yield(&block)
34
- rescue Sidekiq::Shutdown
35
- operative = false
36
- raise
37
- ensure
38
- unlock if operative
39
- end
40
-
41
- def after_yield_yield
42
- yield
43
- end
44
-
45
- def while_executing
46
- lock.synchronize do
47
- yield
48
- end
49
- rescue SidekiqUniqueJobs::RunLockFailed
50
- return reschedule if reschedule_on_lock_fail
51
- raise
52
- end
53
-
54
- def until_timeout
55
- yield if block_given?
56
- end
57
-
58
25
  protected
59
26
 
60
- def unlock
61
- after_unlock_hook if lock.unlock(:server)
62
- end
63
-
64
27
  def after_unlock_hook
65
- worker.after_unlock if worker.respond_to?(:after_unlock)
28
+ -> { worker.after_unlock if worker.respond_to?(:after_unlock) }
66
29
  end
67
30
 
68
31
  def reschedule
@@ -37,7 +37,30 @@ module Sidekiq
37
37
  end
38
38
 
39
39
  class ScheduledSet
40
- module UniqueExtension
40
+ # rubocop:disable Style/ClassAndModuleCamelCase
41
+ module UniqueExtension3_0
42
+ def self.included(base)
43
+ base.class_eval do
44
+ include UnlockMethod
45
+ alias_method :delete_orig, :delete
46
+ alias_method :delete, :delete_ext
47
+ end
48
+ end
49
+
50
+ def delete_ext(score, jid = nil)
51
+ item = find_job(jid)
52
+ unlock(item) if delete_orig(score, jid)
53
+ end
54
+
55
+ def remove_job_ext
56
+ remove_job_orig do |message|
57
+ unlock(Sidekiq.load_json(message))
58
+ yield message
59
+ end
60
+ end
61
+ end
62
+
63
+ module UniqueExtension3_4
41
64
  def self.included(base)
42
65
  base.class_eval do
43
66
  include UnlockMethod
@@ -57,7 +80,11 @@ module Sidekiq
57
80
  end
58
81
  end
59
82
  end
60
- include UniqueExtension if Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new('3.1')
83
+ sidekiq_version = Gem::Version.new(Sidekiq::VERSION)
84
+ include UniqueExtension3_4 if sidekiq_version >= Gem::Version.new('3.4')
85
+ include UniqueExtension3_0 if sidekiq_version >= Gem::Version.new('3.0') &&
86
+ sidekiq_version < Gem::Version.new('3.4')
87
+ # rubocop:enable Style/ClassAndModuleCamelCase
61
88
  end
62
89
 
63
90
  class Job