sidekiq-unique-jobs 6.0.6 → 6.0.25

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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +768 -150
  3. data/README.md +123 -77
  4. data/bin/uniquejobs +2 -2
  5. data/lib/sidekiq-unique-jobs.rb +1 -1
  6. data/lib/sidekiq_unique_jobs/cli.rb +27 -12
  7. data/lib/sidekiq_unique_jobs/client/middleware.rb +8 -4
  8. data/lib/sidekiq_unique_jobs/constants.rb +29 -18
  9. data/lib/sidekiq_unique_jobs/digests.rb +34 -19
  10. data/lib/sidekiq_unique_jobs/job.rb +29 -0
  11. data/lib/sidekiq_unique_jobs/lock/base_lock.rb +14 -18
  12. data/lib/sidekiq_unique_jobs/lock/until_and_while_executing.rb +20 -4
  13. data/lib/sidekiq_unique_jobs/lock/until_executed.rb +7 -3
  14. data/lib/sidekiq_unique_jobs/lock/until_executing.rb +1 -1
  15. data/lib/sidekiq_unique_jobs/lock/until_expired.rb +1 -0
  16. data/lib/sidekiq_unique_jobs/lock/while_executing.rb +9 -3
  17. data/lib/sidekiq_unique_jobs/lock/while_executing_reject.rb +0 -8
  18. data/lib/sidekiq_unique_jobs/locksmith.rb +59 -32
  19. data/lib/sidekiq_unique_jobs/logging.rb +14 -0
  20. data/lib/sidekiq_unique_jobs/middleware.rb +26 -9
  21. data/lib/sidekiq_unique_jobs/normalizer.rb +1 -1
  22. data/lib/sidekiq_unique_jobs/on_conflict/raise.rb +1 -1
  23. data/lib/sidekiq_unique_jobs/on_conflict/reject.rb +3 -3
  24. data/lib/sidekiq_unique_jobs/on_conflict/replace.rb +7 -2
  25. data/lib/sidekiq_unique_jobs/on_conflict/strategy.rb +1 -1
  26. data/lib/sidekiq_unique_jobs/on_conflict.rb +12 -7
  27. data/lib/sidekiq_unique_jobs/options_with_fallback.rb +8 -8
  28. data/lib/sidekiq_unique_jobs/scripts.rb +38 -9
  29. data/lib/sidekiq_unique_jobs/server/middleware.rb +18 -6
  30. data/lib/sidekiq_unique_jobs/sidekiq_unique_ext.rb +1 -1
  31. data/lib/sidekiq_unique_jobs/sidekiq_unique_jobs.rb +88 -0
  32. data/lib/sidekiq_unique_jobs/sidekiq_worker_methods.rb +13 -4
  33. data/lib/sidekiq_unique_jobs/testing.rb +3 -3
  34. data/lib/sidekiq_unique_jobs/timeout/calculator.rb +2 -1
  35. data/lib/sidekiq_unique_jobs/timeout.rb +1 -1
  36. data/lib/sidekiq_unique_jobs/unique_args.rb +7 -4
  37. data/lib/sidekiq_unique_jobs/util.rb +8 -4
  38. data/lib/sidekiq_unique_jobs/version.rb +1 -1
  39. data/lib/sidekiq_unique_jobs/version_check.rb +95 -0
  40. data/lib/sidekiq_unique_jobs/web/helpers.rb +7 -6
  41. data/lib/sidekiq_unique_jobs/web/views/unique_digests.erb +4 -0
  42. data/lib/sidekiq_unique_jobs/web.rb +19 -12
  43. data/lib/sidekiq_unique_jobs.rb +33 -107
  44. data/lib/tasks/changelog.rake +23 -0
  45. data/redis/convert_legacy_lock.lua +13 -0
  46. data/redis/delete_by_digest.lua +10 -11
  47. data/redis/delete_job_by_digest.lua +6 -4
  48. data/redis/lock.lua +14 -10
  49. data/redis/unlock.lua +13 -11
  50. metadata +88 -108
  51. data/.codeclimate.yml +0 -35
  52. data/.csslintrc +0 -2
  53. data/.dockerignore +0 -4
  54. data/.editorconfig +0 -14
  55. data/.eslintignore +0 -1
  56. data/.eslintrc +0 -213
  57. data/.fasterer.yml +0 -23
  58. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -31
  59. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
  60. data/.gitignore +0 -26
  61. data/.reek.yml +0 -87
  62. data/.rspec +0 -2
  63. data/.rubocop.yml +0 -127
  64. data/.simplecov +0 -20
  65. data/.travis.yml +0 -49
  66. data/.yardopts +0 -7
  67. data/Appraisals +0 -25
  68. data/CODE_OF_CONDUCT.md +0 -74
  69. data/Gemfile +0 -24
  70. data/Guardfile +0 -55
  71. data/Rakefile +0 -12
  72. data/_config.yml +0 -1
  73. data/assets/unique_digests_1.png +0 -0
  74. data/assets/unique_digests_2.png +0 -0
  75. data/bin/bench +0 -20
  76. data/examples/another_unique_job.rb +0 -15
  77. data/examples/custom_queue_job.rb +0 -12
  78. data/examples/custom_queue_job_with_filter_method.rb +0 -13
  79. data/examples/custom_queue_job_with_filter_proc.rb +0 -16
  80. data/examples/expiring_job.rb +0 -12
  81. data/examples/inline_worker.rb +0 -12
  82. data/examples/just_a_worker.rb +0 -13
  83. data/examples/long_running_job.rb +0 -14
  84. data/examples/main_job.rb +0 -14
  85. data/examples/my_job.rb +0 -12
  86. data/examples/my_unique_job.rb +0 -15
  87. data/examples/my_unique_job_with_filter_method.rb +0 -21
  88. data/examples/my_unique_job_with_filter_proc.rb +0 -19
  89. data/examples/notify_worker.rb +0 -14
  90. data/examples/plain_class.rb +0 -13
  91. data/examples/simple_worker.rb +0 -15
  92. data/examples/spawn_simple_worker.rb +0 -12
  93. data/examples/test_class.rb +0 -9
  94. data/examples/unique_across_workers_job.rb +0 -20
  95. data/examples/unique_job_on_conflict_raise.rb +0 -14
  96. data/examples/unique_job_on_conflict_reject.rb +0 -14
  97. data/examples/unique_job_on_conflict_reschedule.rb +0 -14
  98. data/examples/unique_job_with_conditional_parameter.rb +0 -18
  99. data/examples/unique_job_with_filter_method.rb +0 -21
  100. data/examples/unique_job_with_nil_unique_args.rb +0 -20
  101. data/examples/unique_job_with_no_unique_args_method.rb +0 -16
  102. data/examples/unique_job_withthout_unique_args_parameter.rb +0 -18
  103. data/examples/unique_on_all_queues_job.rb +0 -16
  104. data/examples/until_and_while_executing_job.rb +0 -17
  105. data/examples/until_executed_2_job.rb +0 -24
  106. data/examples/until_executed_job.rb +0 -25
  107. data/examples/until_executing_job.rb +0 -11
  108. data/examples/until_expired_job.rb +0 -12
  109. data/examples/until_global_expired_job.rb +0 -12
  110. data/examples/while_executing_job.rb +0 -15
  111. data/examples/while_executing_reject_job.rb +0 -14
  112. data/examples/without_argument_job.rb +0 -13
  113. data/sidekiq-unique-jobs.gemspec +0 -42
@@ -13,9 +13,10 @@ module SidekiqUniqueJobs
13
13
  # @param [Proc] callback the callback to use after unlock
14
14
  # @param [Sidekiq::RedisConnection, ConnectionPool] redis_pool the redis connection
15
15
  def initialize(item, callback, redis_pool = nil)
16
- @item = prepare_item(item)
16
+ @item = item
17
17
  @callback = callback
18
18
  @redis_pool = redis_pool
19
+ add_uniqueness_when_missing # Used to ease testing
19
20
  end
20
21
 
21
22
  # Handles locking of sidekiq jobs.
@@ -65,6 +66,14 @@ module SidekiqUniqueJobs
65
66
 
66
67
  private
67
68
 
69
+ def add_uniqueness_when_missing
70
+ return if item.key?(UNIQUE_DIGEST_KEY)
71
+
72
+ # The below should only be done to ease testing
73
+ # in production this will be done by the middleware
74
+ SidekiqUniqueJobs::Job.add_uniqueness(item)
75
+ end
76
+
68
77
  def call_strategy
69
78
  @attempt += 1
70
79
  strategy.call { lock if replace? }
@@ -99,36 +108,23 @@ module SidekiqUniqueJobs
99
108
  def with_cleanup
100
109
  yield
101
110
  rescue Sidekiq::Shutdown
102
- notify_about_manual_unlock
111
+ log_info("Sidekiq is shutting down, the job `should` be put back on the queue. Keeping the lock!")
103
112
  raise
104
113
  else
105
114
  unlock_with_callback
106
115
  end
107
116
 
108
- def prepare_item(item)
109
- calculator = SidekiqUniqueJobs::Timeout::Calculator.new(item)
110
- item[LOCK_TIMEOUT_KEY] = calculator.lock_timeout
111
- item[LOCK_EXPIRATION_KEY] = calculator.lock_expiration
112
- SidekiqUniqueJobs::UniqueArgs.digest(item)
113
- item
114
- end
115
-
116
- def notify_about_manual_unlock
117
- log_fatal("the unique_key: #{item[UNIQUE_DIGEST_KEY]} needs to be unlocked manually")
118
- false
119
- end
120
-
121
117
  def unlock_with_callback
122
- return notify_about_manual_unlock unless unlock
118
+ return log_warn("might need to be unlocked manually") unless unlock
123
119
 
124
120
  callback_safely
125
- item[JID_KEY]
126
121
  end
127
122
 
128
123
  def callback_safely
129
124
  callback&.call
125
+ item[JID_KEY]
130
126
  rescue StandardError
131
- log_warn("The unique_key: #{item[UNIQUE_DIGEST_KEY]} has been unlocked but the #after_unlock callback failed!")
127
+ log_warn("unlocked successfully but the #after_unlock callback failed!")
132
128
  raise
133
129
  end
134
130
 
@@ -16,15 +16,31 @@ module SidekiqUniqueJobs
16
16
  # Executes in the Sidekiq server process
17
17
  # @yield to the worker class perform method
18
18
  def execute
19
- return unless locked?
20
- unlock
21
-
22
- runtime_lock.execute { yield }
19
+ if unlock
20
+ lock_on_failure do
21
+ runtime_lock.execute { return yield }
22
+ end
23
+ else
24
+ log_warn "couldn't unlock digest: #{item[UNIQUE_DIGEST_KEY]} #{item[JID_KEY]}"
25
+ end
26
+ ensure
27
+ runtime_lock.delete!
23
28
  end
24
29
 
25
30
  def runtime_lock
26
31
  @runtime_lock ||= SidekiqUniqueJobs::Lock::WhileExecuting.new(item, callback, redis_pool)
27
32
  end
33
+
34
+ private
35
+
36
+ def lock_on_failure
37
+ yield
38
+ runtime_lock.delete!
39
+ rescue Exception # rubocop:disable Lint/RescueException
40
+ log_error("Failed to execute job, restoring lock")
41
+ lock
42
+ raise
43
+ end
28
44
  end
29
45
  end
30
46
  end
@@ -8,13 +8,17 @@ module SidekiqUniqueJobs
8
8
  #
9
9
  # @author Mikael Henriksson <mikael@zoolutions.se>
10
10
  class UntilExecuted < BaseLock
11
- OK ||= 'OK'
11
+ OK ||= "OK"
12
12
 
13
13
  # Executes in the Sidekiq server process
14
14
  # @yield to the worker class perform method
15
15
  def execute
16
- return unless locked?
17
- with_cleanup { yield }
16
+ if locked?
17
+ with_cleanup { yield }
18
+ else
19
+ log_warn "the unique_key: #{item[UNIQUE_DIGEST_KEY]} is not locked, allowing job to silently complete"
20
+ nil
21
+ end
18
22
  end
19
23
  end
20
24
  end
@@ -4,7 +4,7 @@ module SidekiqUniqueJobs
4
4
  class Lock
5
5
  # Locks jobs until {#execute} starts
6
6
  # - Locks on perform_in or perform_async
7
- # - Unlocks after yielding to the worker's perform method
7
+ # - Unlocks before yielding to the worker's perform method
8
8
  #
9
9
  # @author Mikael Henriksson <mikael@zoolutions.se>
10
10
  class UntilExecuting < BaseLock
@@ -21,6 +21,7 @@ module SidekiqUniqueJobs
21
21
  # @yield to the worker class perform method
22
22
  def execute
23
23
  return unless locked?
24
+
24
25
  yield
25
26
  # this lock does not handle after_unlock since we don't know when that would happen
26
27
  end
@@ -11,11 +11,12 @@ module SidekiqUniqueJobs
11
11
  #
12
12
  # @author Mikael Henriksson <mikael@zoolutions.se>
13
13
  class WhileExecuting < BaseLock
14
- RUN_SUFFIX ||= ':RUN'
14
+ RUN_SUFFIX ||= ":RUN"
15
15
 
16
16
  # @param [Hash] item the Sidekiq job hash
17
17
  # @param [Proc] callback callback to call after unlock
18
18
  # @param [Sidekiq::RedisConnection, ConnectionPool] redis_pool the redis connection
19
+ #
19
20
  def initialize(item, callback, redis_pool = nil)
20
21
  super(item, callback, redis_pool)
21
22
  append_unique_key_suffix
@@ -32,8 +33,13 @@ module SidekiqUniqueJobs
32
33
  # These jobs are locked in the server process not from the client
33
34
  # @yield to the worker class perform method
34
35
  def execute
35
- return strategy.call unless locksmith.lock(item[LOCK_TIMEOUT_KEY])
36
- with_cleanup { yield }
36
+ return strategy&.call unless locksmith.lock(item[LOCK_TIMEOUT_KEY])
37
+
38
+ yield
39
+ unlock_with_callback
40
+ rescue Exception # rubocop:disable Lint/RescueException
41
+ delete!
42
+ raise
37
43
  end
38
44
 
39
45
  private
@@ -11,14 +11,6 @@ module SidekiqUniqueJobs
11
11
  #
12
12
  # @author Mikael Henriksson <mikael@zoolutions.se>
13
13
  class WhileExecutingReject < WhileExecuting
14
- # Executes in the Sidekiq server process
15
- # @yield to the worker class perform method
16
- def execute
17
- return strategy.call unless locksmith.lock(item[LOCK_TIMEOUT_KEY])
18
-
19
- with_cleanup { yield }
20
- end
21
-
22
14
  # Overridden with a forced {OnConflict::Reject} strategy
23
15
  # @return [OnConflict::Reject] a reject strategy
24
16
  def strategy
@@ -4,6 +4,7 @@ module SidekiqUniqueJobs
4
4
  # Lock manager class that handles all the various locks
5
5
  #
6
6
  # @author Mikael Henriksson <mikael@zoolutions.se>
7
+ # rubocop:disable Metrics/ClassLength
7
8
  class Locksmith
8
9
  include SidekiqUniqueJobs::Connection
9
10
 
@@ -13,34 +14,26 @@ module SidekiqUniqueJobs
13
14
  # @option item [String] :unique_digest the unique digest (See: {UniqueArgs#unique_digest})
14
15
  # @param [Sidekiq::RedisConnection, ConnectionPool] redis_pool the redis connection
15
16
  def initialize(item, redis_pool = nil)
16
- @concurrency = 1 # removed in a0cff5bc42edbe7190d6ede7e7f845074d2d7af6
17
- @expiration = item[LOCK_EXPIRATION_KEY]
17
+ # @concurrency = 1 # removed in a0cff5bc42edbe7190d6ede7e7f845074d2d7af6
18
+ @ttl = item[LOCK_EXPIRATION_KEY] || item[LOCK_TTL_KEY]
18
19
  @jid = item[JID_KEY]
19
- @unique_digest = item[UNIQUE_DIGEST_KEY]
20
+ @unique_digest = item[UNIQUE_DIGEST_KEY] || item[LOCK_DIGEST_KEY]
21
+ @lock_type = item[LOCK_KEY] || item[UNIQUE_KEY]
22
+ @lock_type &&= @lock_type.to_sym
20
23
  @redis_pool = redis_pool
21
24
  end
22
25
 
23
- # Checks if the exists key is created in redis
24
- # @return [true, false]
25
- def exists?
26
- redis(redis_pool) { |conn| conn.exists(exists_key) }
27
- end
28
-
29
- # The number of available resourced for this lock
30
- # @return [Integer] the number of available resources
31
- def available_count
32
- return concurrency unless exists?
33
-
34
- redis(redis_pool) { |conn| conn.llen(available_key) }
35
- end
36
-
37
- # Deletes the lock unless it has an expiration set
26
+ #
27
+ # Deletes the lock unless it has a ttl set
28
+ #
29
+ #
38
30
  def delete
39
- return if expiration
31
+ return if ttl
32
+
40
33
  delete!
41
34
  end
42
35
 
43
- # Deletes the lock regardless of if it has an expiration set
36
+ # Deletes the lock regardless of if it has a ttl set
44
37
  def delete!
45
38
  Scripts.call(
46
39
  :delete,
@@ -49,15 +42,18 @@ module SidekiqUniqueJobs
49
42
  )
50
43
  end
51
44
 
45
+ #
52
46
  # Create a lock for the item
47
+ #
53
48
  # @param [Integer] timeout the number of seconds to wait for a lock.
54
- # nil means wait indefinitely
55
- # @yield the block to execute if a lock is successful
56
- # @return the Sidekiq job_id (jid)
49
+ #
50
+ # @return [String] the Sidekiq job_id (jid)
51
+ #
52
+ #
57
53
  def lock(timeout = nil, &block)
58
54
  Scripts.call(:lock, redis_pool,
59
55
  keys: [exists_key, grabbed_key, available_key, UNIQUE_SET, unique_digest],
60
- argv: [jid, expiration])
56
+ argv: [jid, ttl, lock_type])
61
57
 
62
58
  grab_token(timeout) do |token|
63
59
  touch_grabbed_token(token)
@@ -66,18 +62,27 @@ module SidekiqUniqueJobs
66
62
  end
67
63
  alias wait lock
68
64
 
65
+ #
69
66
  # Removes the lock keys from Redis if locked by the provided jid/token
67
+ #
70
68
  # @return [false] unless locked?
71
69
  # @return [String] Sidekiq job_id (jid) if successful
70
+ #
72
71
  def unlock(token = nil)
73
72
  token ||= jid
74
73
  return false unless locked?(token)
74
+
75
75
  unlock!(token)
76
76
  end
77
77
 
78
+ #
78
79
  # Removes the lock keys from Redis
80
+ #
81
+ # @param [String] token the token to unlock (defaults to jid)
82
+ #
79
83
  # @return [false] unless locked?
80
84
  # @return [String] Sidekiq job_id (jid) if successful
85
+ #
81
86
  def unlock!(token = nil)
82
87
  token ||= jid
83
88
 
@@ -85,22 +90,40 @@ module SidekiqUniqueJobs
85
90
  :unlock,
86
91
  redis_pool,
87
92
  keys: [exists_key, grabbed_key, available_key, version_key, UNIQUE_SET, unique_digest],
88
- argv: [token, expiration],
93
+ argv: [token, ttl, lock_type],
89
94
  )
90
95
  end
91
96
 
92
- # Checks if this instance is considered locked
97
+ #
93
98
  # @param [String] token the unique token to check for a lock.
94
99
  # nil will default to the jid provided in the initializer
95
100
  # @return [true, false]
101
+ #
102
+ # Checks if this instance is considered locked
103
+ #
104
+ # @param [<type>] token <description>
105
+ #
106
+ # @return [<type>] <description>
107
+ #
96
108
  def locked?(token = nil)
97
109
  token ||= jid
110
+
111
+ convert_legacy_lock(token)
98
112
  redis(redis_pool) { |conn| conn.hexists(grabbed_key, token) }
99
113
  end
100
114
 
101
115
  private
102
116
 
103
- attr_reader :concurrency, :unique_digest, :expiration, :jid, :redis_pool
117
+ attr_reader :unique_digest, :ttl, :jid, :redis_pool, :lock_type
118
+
119
+ def convert_legacy_lock(token)
120
+ Scripts.call(
121
+ :convert_legacy_lock,
122
+ redis_pool,
123
+ keys: [grabbed_key, unique_digest],
124
+ argv: [token, current_time.to_f],
125
+ )
126
+ end
104
127
 
105
128
  def grab_token(timeout = nil)
106
129
  redis(redis_pool) do |conn|
@@ -116,7 +139,10 @@ module SidekiqUniqueJobs
116
139
  end
117
140
 
118
141
  def touch_grabbed_token(token)
119
- redis(redis_pool) { |conn| conn.hset(grabbed_key, token, current_time.to_f) }
142
+ redis(redis_pool) do |conn|
143
+ conn.hset(grabbed_key, token, current_time.to_f)
144
+ conn.expire(grabbed_key, ttl) if ttl && lock_type == :until_expired
145
+ end
120
146
  end
121
147
 
122
148
  def return_token_or_block_value(token)
@@ -131,19 +157,19 @@ module SidekiqUniqueJobs
131
157
  end
132
158
 
133
159
  def available_key
134
- @available_key ||= namespaced_key('AVAILABLE')
160
+ @available_key ||= namespaced_key("AVAILABLE")
135
161
  end
136
162
 
137
163
  def exists_key
138
- @exists_key ||= namespaced_key('EXISTS')
164
+ @exists_key ||= namespaced_key("EXISTS")
139
165
  end
140
166
 
141
167
  def grabbed_key
142
- @grabbed_key ||= namespaced_key('GRABBED')
168
+ @grabbed_key ||= namespaced_key("GRABBED")
143
169
  end
144
170
 
145
171
  def version_key
146
- @version_key ||= namespaced_key('VERSION')
172
+ @version_key ||= namespaced_key("VERSION")
147
173
  end
148
174
 
149
175
  def namespaced_key(variable)
@@ -159,4 +185,5 @@ module SidekiqUniqueJobs
159
185
  redis(&:time)
160
186
  end
161
187
  end
188
+ # rubocop:enable Metrics/ClassLength
162
189
  end
@@ -16,6 +16,7 @@ module SidekiqUniqueJobs
16
16
  # Used for compatibility with logger
17
17
  def log_debug(message_or_exception = nil, &block)
18
18
  logger.debug(message_or_exception, &block)
19
+ nil
19
20
  end
20
21
 
21
22
  # Logs a message at info level
@@ -24,6 +25,7 @@ module SidekiqUniqueJobs
24
25
  # Used for compatibility with logger
25
26
  def log_info(message_or_exception = nil, &block)
26
27
  logger.info(message_or_exception, &block)
28
+ nil
27
29
  end
28
30
 
29
31
  # Logs a message at warn level
@@ -32,6 +34,7 @@ module SidekiqUniqueJobs
32
34
  # Used for compatibility with logger
33
35
  def log_warn(message_or_exception = nil, &block)
34
36
  logger.warn(message_or_exception, &block)
37
+ nil
35
38
  end
36
39
 
37
40
  # Logs a message at error level
@@ -40,6 +43,7 @@ module SidekiqUniqueJobs
40
43
  # Used for compatibility with logger
41
44
  def log_error(message_or_exception = nil, &block)
42
45
  logger.error(message_or_exception, &block)
46
+ nil
43
47
  end
44
48
 
45
49
  # Logs a message at fatal level
@@ -48,6 +52,16 @@ module SidekiqUniqueJobs
48
52
  # Used for compatibility with logger
49
53
  def log_fatal(message_or_exception = nil, &block)
50
54
  logger.fatal(message_or_exception, &block)
55
+ nil
56
+ end
57
+
58
+ def logging_context(middleware_class, job_hash)
59
+ digest = job_hash["unique_digest"]
60
+ if defined?(Sidekiq::Logging)
61
+ "#{middleware_class} #{"DIG-#{digest}" if digest}"
62
+ else
63
+ { middleware: middleware_class, unique_digest: digest }
64
+ end
51
65
  end
52
66
  end
53
67
  end
@@ -1,8 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'sidekiq'
3
+ require "sidekiq"
4
4
 
5
5
  module SidekiqUniqueJobs
6
+ #
7
+ # Provides the sidekiq middleware that makes the gem work
8
+ #
9
+ # @author Mikael Henriksson <mikael@zoolutions.se>
10
+ #
6
11
  module Middleware
7
12
  def self.extended(base)
8
13
  base.class_eval do
@@ -15,16 +20,24 @@ module SidekiqUniqueJobs
15
20
  configure_client_middleware
16
21
  end
17
22
 
18
- def configure_server_middleware
23
+ def configure_server_middleware # rubocop:disable Metrics/MethodLength
19
24
  Sidekiq.configure_server do |config|
20
25
  config.client_middleware do |chain|
21
- require 'sidekiq_unique_jobs/client/middleware'
22
- chain.add SidekiqUniqueJobs::Client::Middleware
26
+ require "sidekiq_unique_jobs/client/middleware"
27
+ if defined?(Apartment::Sidekiq::Middleware::Client)
28
+ chain.insert_after Apartment::Sidekiq::Middleware::Client, SidekiqUniqueJobs::Client::Middleware
29
+ else
30
+ chain.add SidekiqUniqueJobs::Client::Middleware
31
+ end
23
32
  end
24
33
 
25
34
  config.server_middleware do |chain|
26
- require 'sidekiq_unique_jobs/server/middleware'
27
- chain.add SidekiqUniqueJobs::Server::Middleware
35
+ require "sidekiq_unique_jobs/server/middleware"
36
+ if defined?(Apartment::Sidekiq::Middleware::Server)
37
+ chain.insert_after Apartment::Sidekiq::Middleware::Server, SidekiqUniqueJobs::Server::Middleware
38
+ else
39
+ chain.add SidekiqUniqueJobs::Server::Middleware
40
+ end
28
41
  end
29
42
  end
30
43
  end
@@ -32,11 +45,15 @@ module SidekiqUniqueJobs
32
45
  def configure_client_middleware
33
46
  Sidekiq.configure_client do |config|
34
47
  config.client_middleware do |chain|
35
- require 'sidekiq_unique_jobs/client/middleware'
36
- chain.add SidekiqUniqueJobs::Client::Middleware
48
+ require "sidekiq_unique_jobs/client/middleware"
49
+ if defined?(Apartment::Sidekiq::Middleware::Client)
50
+ chain.insert_after Apartment::Sidekiq::Middleware::Client, SidekiqUniqueJobs::Client::Middleware
51
+ else
52
+ chain.add SidekiqUniqueJobs::Client::Middleware
53
+ end
37
54
  end
38
55
  end
39
56
  end
40
57
  end
41
58
  end
42
- SidekiqUniqueJobs.send(:extend, SidekiqUniqueJobs::Middleware)
59
+ SidekiqUniqueJobs.extend SidekiqUniqueJobs::Middleware
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ require "json"
4
4
 
5
5
  module SidekiqUniqueJobs
6
6
  # Normalizes hashes by dumping them to json and loading them from json
@@ -10,7 +10,7 @@ module SidekiqUniqueJobs
10
10
  # This will cause Sidekiq to retry the job
11
11
  # @raise [SidekiqUniqueJobs::Conflict]
12
12
  def call
13
- fail SidekiqUniqueJobs::Conflict, item
13
+ raise SidekiqUniqueJobs::Conflict, item
14
14
  end
15
15
  end
16
16
  end
@@ -53,9 +53,9 @@ module SidekiqUniqueJobs
53
53
  def push_to_deadset
54
54
  Sidekiq.redis do |conn|
55
55
  conn.multi do
56
- conn.zadd('dead', current_time, payload)
57
- conn.zremrangebyscore('dead', '-inf', current_time - Sidekiq::DeadSet.timeout)
58
- conn.zremrangebyrank('dead', 0, -Sidekiq::DeadSet.max_jobs)
56
+ conn.zadd("dead", current_time, payload)
57
+ conn.zremrangebyscore("dead", "-inf", current_time - Sidekiq::DeadSet.timeout)
58
+ conn.zremrangebyrank("dead", 0, -Sidekiq::DeadSet.max_jobs)
59
59
  end
60
60
  end
61
61
  end
@@ -19,18 +19,23 @@ module SidekiqUniqueJobs
19
19
  # @yield to retry the lock after deleting the old one
20
20
  def call(&block)
21
21
  return unless delete_job_by_digest
22
+
22
23
  delete_lock
23
24
  block&.call
24
25
  end
25
26
 
26
27
  # Delete the job from either schedule, retry or the queue
27
28
  def delete_job_by_digest
28
- Scripts.call(:delete_job_by_digest, nil, keys: [queue, unique_digest])
29
+ Scripts.call(
30
+ :delete_job_by_digest,
31
+ nil,
32
+ keys: ["#{QUEUE_KEY}:#{queue}", SCHEDULE_SET, RETRY_SET], argv: [unique_digest],
33
+ )
29
34
  end
30
35
 
31
36
  # Delete the keys belonging to the job
32
37
  def delete_lock
33
- Scripts.call(:delete_by_digest, nil, keys: [UNIQUE_SET, unique_digest])
38
+ SidekiqUniqueJobs::Digests.delete_by_digest(unique_digest)
34
39
  end
35
40
  end
36
41
  end
@@ -21,7 +21,7 @@ module SidekiqUniqueJobs
21
21
  # Use strategy on conflict
22
22
  # @raise [NotImplementedError] needs to be implemented in child class
23
23
  def call
24
- fail NotImplementedError, 'needs to be implemented in child class'
24
+ raise NotImplementedError, "needs to be implemented in child class"
25
25
  end
26
26
 
27
27
  def replace?
@@ -1,14 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'on_conflict/strategy'
4
- require_relative 'on_conflict/null_strategy'
5
- require_relative 'on_conflict/log'
6
- require_relative 'on_conflict/raise'
7
- require_relative 'on_conflict/reject'
8
- require_relative 'on_conflict/replace'
9
- require_relative 'on_conflict/reschedule'
3
+ require_relative "on_conflict/strategy"
4
+ require_relative "on_conflict/null_strategy"
5
+ require_relative "on_conflict/log"
6
+ require_relative "on_conflict/raise"
7
+ require_relative "on_conflict/reject"
8
+ require_relative "on_conflict/replace"
9
+ require_relative "on_conflict/reschedule"
10
10
 
11
11
  module SidekiqUniqueJobs
12
+ #
13
+ # Provides lock conflict resolutions
14
+ #
15
+ # @author Mikael Henriksson <mikael@zoolutions.se>
16
+ #
12
17
  module OnConflict
13
18
  STRATEGIES = {
14
19
  log: OnConflict::Log,
@@ -11,12 +11,12 @@ module SidekiqUniqueJobs
11
11
  module OptionsWithFallback
12
12
  LOCKS = {
13
13
  until_and_while_executing: SidekiqUniqueJobs::Lock::UntilAndWhileExecuting,
14
- until_executed: SidekiqUniqueJobs::Lock::UntilExecuted,
15
- until_executing: SidekiqUniqueJobs::Lock::UntilExecuting,
16
- until_expired: SidekiqUniqueJobs::Lock::UntilExpired,
17
- until_timeout: SidekiqUniqueJobs::Lock::UntilExpired,
18
- while_executing: SidekiqUniqueJobs::Lock::WhileExecuting,
19
- while_executing_reject: SidekiqUniqueJobs::Lock::WhileExecutingReject,
14
+ until_executed: SidekiqUniqueJobs::Lock::UntilExecuted,
15
+ until_executing: SidekiqUniqueJobs::Lock::UntilExecuting,
16
+ until_expired: SidekiqUniqueJobs::Lock::UntilExpired,
17
+ until_timeout: SidekiqUniqueJobs::Lock::UntilExpired,
18
+ while_executing: SidekiqUniqueJobs::Lock::WhileExecuting,
19
+ while_executing_reject: SidekiqUniqueJobs::Lock::WhileExecutingReject,
20
20
  }.freeze
21
21
 
22
22
  def self.included(base)
@@ -50,14 +50,14 @@ module SidekiqUniqueJobs
50
50
  def lock_class
51
51
  @lock_class ||= begin
52
52
  LOCKS.fetch(lock_type.to_sym) do
53
- fail UnknownLock, "No implementation for `lock: :#{lock_type}`"
53
+ raise UnknownLock, "No implementation for `lock: :#{lock_type}`"
54
54
  end
55
55
  end
56
56
  end
57
57
 
58
58
  # @return [Symbol]
59
59
  def lock_type
60
- @lock_type ||= options[LOCK_KEY] || item[LOCK_KEY] || unique_type
60
+ @lock_type ||= item[LOCK_KEY] || options[LOCK_KEY] || unique_type
61
61
  end
62
62
 
63
63
  def unique_type