activejob-unique 0.5.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 061e508b94c278bf8ccd34aa73f19461625922aec84783d6337968342ef7a8db
4
+ data.tar.gz: 135d045c623b181d2bb86bd2234b72f25c84f7c4549829e4d40d0908fd4dd838
5
+ SHA512:
6
+ metadata.gz: 14b1956520105caeef38dcb7e5c4b5f51dbc197d575f3f839d6d9d5a7ca2502cda85ebeacf56b7dff206589566bc1680fa20707d7437a74c95509706e4ce253b
7
+ data.tar.gz: a821c7491fd5f0768863d0ed475ea72f81ee318ce19ac5999765c6bde3f156bda1e78baeda5390e0fb0dc73d14f9453b20bc10365f9a5c0352f4452a2dcf9b1e
data/CHANGELOG.md ADDED
@@ -0,0 +1,128 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5
+
6
+ ## [Unreleased](https://github.com/nordinvestments/activejob-uniqueness/compare/v0.5.0...HEAD)
7
+
8
+ ## [0.5.0](https://github.com/nordinvestments/activejob-uniqueness/compare/v0.4.0...v0.5.0) - 2026-01-12
9
+
10
+ ### Changed
11
+ - Fork published as `activejob-unique` gem by [@nordinvestments](https://github.com/nordinvestments)
12
+ - Updated gem metadata to point to fork repository
13
+
14
+ ---
15
+
16
+ *Versions below are from the original [veeqo/activejob-uniqueness](https://github.com/veeqo/activejob-uniqueness) repository.*
17
+
18
+ ## [0.4.0](https://github.com/veeqo/activejob-uniqueness/compare/v0.3.2...v0.4.0) - 2024-12-07
19
+
20
+ ### Added
21
+
22
+ - [#86](https://github.com/veeqo/activejob-uniqueness/pull/86) Add Rails 8.0 rc1 support by[@sharshenov](https://github.com/sharshenov)
23
+ - [#78](https://github.com/veeqo/activejob-uniqueness/pull/78) Add on_redis_connection_error config to adjust to new redlock behaviour by[@nduitz](https://github.com/nduitz)
24
+
25
+ ### Changed
26
+ - [#82](https://github.com/veeqo/activejob-uniqueness/pull/82) Optimize bulk unlocking [@sharshenov](https://github.com/sharshenov)
27
+
28
+ ## [0.3.2](https://github.com/veeqo/activejob-uniqueness/compare/v0.3.1...v0.3.2) - 2024-08-16
29
+
30
+ ### Added
31
+ - [#80](https://github.com/veeqo/activejob-uniqueness/pull/80) Add rails 7.2 support by [@viralpraxis](https://github.com/viralpraxis)
32
+
33
+ ### Changed
34
+ - [#74](https://github.com/veeqo/activejob-uniqueness/pull/74) Fix log subscriber by [@shahidkhaliq](https://github.com/shahidkhaliq)
35
+
36
+ ## [0.3.1](https://github.com/veeqo/activejob-uniqueness/compare/v0.3.0...v0.3.1) - 2023-10-30
37
+
38
+ ### Fixed
39
+
40
+ - [#67](https://github.com/veeqo/activejob-uniqueness/pull/67) Random redis errors on delete_lock by [@laurafeier](https://github.com/laurafeier)
41
+
42
+ ## [0.3.0](https://github.com/veeqo/activejob-uniqueness/compare/v0.2.5...v0.3.0) - 2023-10-20
43
+
44
+ ### Added
45
+ - [#66](https://github.com/veeqo/activejob-uniqueness/pull/66) Activejob 7.1 support by [@laurafeier](https://github.com/laurafeier)
46
+
47
+ ### Changed
48
+ - [#57](https://github.com/veeqo/activejob-uniqueness/pull/57) Upgrade to Redlock 2 & use redis-client by [@bmulholland](https://github.com/bmulholland)
49
+
50
+ ### Removed
51
+ - Support fo Redlock v1 is removed. Switching to `RedisClient` is [a breaking change of Redlock v2](https://github.com/leandromoreira/redlock-rb/blob/main/CHANGELOG.md#200---2023-02-09).
52
+
53
+ ## [0.2.5](https://github.com/veeqo/activejob-uniqueness/compare/v0.2.4...v0.2.5) - 2023-02-01
54
+
55
+ ### Added
56
+ - [#45](https://github.com/veeqo/activejob-uniqueness/pull/45) Add Dependabot for GitHub Actions by [@petergoldstein](https://github.com/petergoldstein)
57
+ - [#51](https://github.com/veeqo/activejob-uniqueness/pull/51) Add support for Sidekiq 7 by [@dwightwatson](https://github.com/dwightwatson)
58
+ - [#52](https://github.com/veeqo/activejob-uniqueness/pull/52) Add Ruby 3.2.0 to the CI matrix by [@petergoldstein](https://github.com/petergoldstein)
59
+
60
+ ### Changed
61
+ - [#46](https://github.com/veeqo/activejob-uniqueness/pull/46) Fix a method name typo in CHANGELOG by [@y-yagi](https://github.com/y-yagi)
62
+
63
+ ## [0.2.4](https://github.com/veeqo/activejob-uniqueness/compare/v0.2.3...v0.2.4) - 2022-06-22
64
+
65
+ ### Added
66
+ - [#43](https://github.com/veeqo/activejob-uniqueness/pull/43) Run rubocop on Github Actions
67
+ - [#44](https://github.com/veeqo/activejob-uniqueness/pull/44) Add ActiveJob::Uniqueness.reset_manager! method to reset lock manager by [@akostadinov](https://github.com/akostadinov)
68
+
69
+ ### Changed
70
+ - [#42](https://github.com/veeqo/activejob-uniqueness/pull/42) Actualize rubies and gems for tests
71
+
72
+ ## [0.2.3](https://github.com/veeqo/activejob-uniqueness/compare/v0.2.2...v0.2.3) - 2022-02-28
73
+
74
+ ### Added
75
+ - [#36](https://github.com/veeqo/activejob-uniqueness/pull/36) Support ActiveJob/Rails 7.0
76
+ - [#37](https://github.com/veeqo/activejob-uniqueness/pull/37) Add Ruby 3.1 to CI by [@petergoldstein](https://github.com/petergoldstein)
77
+
78
+ ## [0.2.2](https://github.com/veeqo/activejob-uniqueness/compare/v0.2.1...v0.2.2) - 2021-10-22
79
+
80
+ ### Added
81
+ - [#32](https://github.com/veeqo/activejob-uniqueness/pull/32) Add ability to set a custom runtime lock key for `:until_and_while_executing` strategy
82
+
83
+ ## [0.2.1](https://github.com/veeqo/activejob-uniqueness/compare/v0.2.0...v0.2.1) - 2021-08-24
84
+
85
+ ### Added
86
+ - [#30](https://github.com/veeqo/activejob-uniqueness/pull/30) Add Sidekiq::JobRecord support (reported by [@dwightwatson](https://github.com/dwightwatson))
87
+
88
+ ## [0.2.0](https://github.com/veeqo/activejob-uniqueness/compare/v0.1.4...v0.2.0) - 2021-05-09
89
+
90
+ ### Added
91
+ - [#22](https://github.com/veeqo/activejob-uniqueness/pull/22) Test with ruby 3.0.1
92
+
93
+ ### Changed
94
+ - [#20](https://github.com/veeqo/activejob-uniqueness/pull/20) **Breaking** Sidekiq patch is not applied automatically anymore
95
+ - [#21](https://github.com/veeqo/activejob-uniqueness/pull/21) Migrate from Travis to Github Actions
96
+ - [#24](https://github.com/veeqo/activejob-uniqueness/pull/24) The default value for `retry_count` of redlock is now 0
97
+ - Require ruby 2.5+
98
+
99
+ ## [0.1.4](https://github.com/veeqo/activejob-uniqueness/compare/v0.1.3...v0.1.4) - 2020-09-22
100
+
101
+ ### Fixed
102
+ - [#11](https://github.com/veeqo/activejob-uniqueness/pull/11) Fix deprecation warnings for ruby 2.7 by [@DanAndreasson](https://github.com/DanAndreasson)
103
+ - [#13](https://github.com/veeqo/activejob-uniqueness/pull/13) Fix deprecation warnings for ruby 2.7
104
+
105
+ ## [0.1.3](https://github.com/veeqo/activejob-uniqueness/compare/v0.1.2...v0.1.3) - 2020-08-17
106
+
107
+ ### Fixed
108
+ - [#7](https://github.com/veeqo/activejob-uniqueness/pull/7) Fix deprecation warnings for ruby 2.7 by [@tonobo](https://github.com/tonobo)
109
+
110
+ ### Changed
111
+ - [#8](https://github.com/veeqo/activejob-uniqueness/pull/8) Use appraisal gem to control gem versions of tests matrix
112
+ - [#9](https://github.com/veeqo/activejob-uniqueness/pull/9) Refactor of Sidekiq API patch. Fixes [#6](https://github.com/veeqo/activejob-uniqueness/issues/6) Rails boot error for version 0.1.2
113
+ - [#10](https://github.com/veeqo/activejob-uniqueness/pull/10) Refactor changelog to comply with Keep a Changelog
114
+
115
+ ## [0.1.2](https://github.com/veeqo/activejob-uniqueness/compare/v0.1.1...v0.1.2) - 2020-07-30
116
+
117
+ ### Added
118
+ - [#5](https://github.com/veeqo/activejob-uniqueness/pull/5) Release lock for Sidekiq adapter by [@vbyno](https://github.com/vbyno)
119
+
120
+ ## [0.1.1](https://github.com/veeqo/activejob-uniqueness/compare/v0.1.0...v0.1.1) - 2020-07-23
121
+
122
+ ### Fixed
123
+ - [#4](https://github.com/veeqo/activejob-uniqueness/pull/4) Fix `NoMethodError` on `Rails.application.eager_load!` in Rails initializer
124
+
125
+ ## [0.1.0](https://github.com/veeqo/activejob-uniqueness/releases/tag/v0.1.0) - 2020-07-05
126
+
127
+ ### Added
128
+ - Job uniqueness for ActiveJob
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Rustam Sharshenov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,266 @@
1
+ # Job uniqueness for ActiveJob
2
+
3
+ > **Maintained fork of [veeqo/activejob-uniqueness](https://github.com/veeqo/activejob-uniqueness)**
4
+ >
5
+ > This gem is published as `activejob-unique` on RubyGems. The API is fully compatible with the original gem — just update your Gemfile.
6
+
7
+ [![Build Status](https://github.com/nordinvestments/activejob-uniqueness/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/nordinvestments/activejob-uniqueness/actions/workflows/main.yml) [![Gem Version](https://badge.fury.io/rb/activejob-unique.svg)](https://badge.fury.io/rb/activejob-unique)
8
+
9
+ ## Requirements
10
+
11
+ | Dependency | Version |
12
+ |------------|---------|
13
+ | Ruby | >= 3.1 |
14
+ | Rails (ActiveJob) | >= 7.1, < 7.3 |
15
+ | Redis | >= 2.8 |
16
+
17
+ > **Note:** This fork targets modern Ruby and Rails versions. For older Ruby (2.5-3.0) or Rails (4.2-7.0) support, use the [original gem](https://github.com/veeqo/activejob-uniqueness).
18
+
19
+ ## Overview
20
+
21
+ The gem allows to protect job uniqueness with next strategies:
22
+
23
+ | Strategy | The job is locked | The job is unlocked |
24
+ |-|-|-|
25
+ | `until_executing` | when **pushed** to the queue | when **processing starts** |
26
+ | `until_executed` | when **pushed** to the queue | when the job is **processed successfully** |
27
+ | `until_expired` | when **pushed** to the queue | when the lock is **expired** |
28
+ | `until_and_while_executing` | when **pushed** to the queue | when **processing starts**<br>a runtime lock is acquired to **prevent simultaneous jobs**<br>*has extra options: `runtime_lock_ttl`, `on_runtime_conflict`* |
29
+ | `while_executing` | when **processing starts** | when the job is **processed**<br>with any result including an error |
30
+
31
+ Inspired by [SidekiqUniqueJobs](https://github.com/mhenrixon/sidekiq-unique-jobs), uses [Redlock](https://github.com/leandromoreira/redlock-rb) under the hood.
32
+
33
+ <p align="center">
34
+ <a href="https://www.veeqo.com/" title="Sponsored by Veeqo">
35
+ <img src="https://static.veeqo.com/assets/sponsored_by_veeqo.png" width="360" />
36
+ </a>
37
+ </p>
38
+
39
+ ## Installation
40
+
41
+ Add the `activejob-unique` gem to your Gemfile.
42
+
43
+ ```ruby
44
+ gem 'activejob-unique'
45
+ ```
46
+
47
+ If you want jobs unlocking for Sidekiq Web UI, require the patch explicitly. [**Queues cleanup becomes slower!**](#sidekiq-api-support)
48
+ ```ruby
49
+ gem 'activejob-unique', require: 'active_job/uniqueness/sidekiq_patch'
50
+ ```
51
+
52
+ And run `bundle install` command.
53
+
54
+ ### Migrating from `activejob-uniqueness`
55
+
56
+ If you're switching from the original gem, simply replace in your Gemfile:
57
+
58
+ ```ruby
59
+ # Before
60
+ gem 'activejob-uniqueness'
61
+
62
+ # After
63
+ gem 'activejob-unique'
64
+ ```
65
+
66
+ No code changes required — the `ActiveJob::Uniqueness` module namespace is unchanged.
67
+
68
+ ## Configuration
69
+
70
+ ActiveJob::Uniqueness is ready to work without any configuration. It will use `REDIS_URL` to connect to Redis instance.
71
+ To override the defaults, create an initializer `config/initializers/active_job_uniqueness.rb` using the following command:
72
+
73
+ ```sh
74
+ rails generate active_job:uniqueness:install
75
+ ```
76
+
77
+ This gem relies on `redlock` for it's Redis connection, that means **it will not inherit global configuration of `Sidekiq`**. To configure the connection, you can use `config.redlock_servers`, for example to disable SSL verification for Redis/Key-Value cloud providers:
78
+
79
+ ```ruby
80
+ ActiveJob::Uniqueness.configure do |config|
81
+ config.redlock_servers = [
82
+ RedisClient.new(
83
+ url: ENV['REDIS_URL'],
84
+ ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE }
85
+ )
86
+ ]
87
+ end
88
+ ```
89
+
90
+ ## Usage
91
+
92
+
93
+ ### Make the job to be unique
94
+
95
+ ```ruby
96
+ class MyJob < ActiveJob::Base
97
+ # new jobs with the same args will raise error until existing one is executed
98
+ unique :until_executed
99
+
100
+ def perform(args)
101
+ # work
102
+ end
103
+ end
104
+ ```
105
+
106
+ ### Tune uniqueness settings per job
107
+
108
+ ```ruby
109
+ class MyJob < ActiveJob::Base
110
+ # new jobs with the same args will be logged within 3 hours or until existing one is being executing
111
+ unique :until_executing, lock_ttl: 3.hours, on_conflict: :log
112
+
113
+ def perform(args)
114
+ # work
115
+ end
116
+ end
117
+ ```
118
+
119
+ You can set defaults globally with [the configuration](#configuration)
120
+
121
+ ### Control lock conflicts
122
+
123
+ ```ruby
124
+ class MyJob < ActiveJob::Base
125
+ # Proc gets the job instance including its arguments
126
+ unique :until_executing, on_conflict: ->(job) { job.logger.info "Oops: #{job.arguments}" }
127
+
128
+ def perform(args)
129
+ # work
130
+ end
131
+ end
132
+ ```
133
+
134
+ ### Control redis connection errors
135
+
136
+ ```ruby
137
+ class MyJob < ActiveJob::Base
138
+ # Proc gets the job instance including its arguments, and as keyword arguments the resource(lock key) `resource` and the original error `error`
139
+ unique :until_executing, on_redis_connection_error: ->(job, resource: _, error: _) { job.logger.info "Oops: #{job.arguments}" }
140
+
141
+ def perform(args)
142
+ # work
143
+ end
144
+ end
145
+ ```
146
+
147
+ ### Control lock key arguments
148
+
149
+ ```ruby
150
+ class MyJob < ActiveJob::Base
151
+ unique :until_executed
152
+
153
+ def perform(foo, bar, baz)
154
+ # work
155
+ end
156
+
157
+ def lock_key_arguments
158
+ arguments.first(2) # baz is ignored
159
+ end
160
+ end
161
+ ```
162
+
163
+ ### Control the lock key
164
+
165
+ ```ruby
166
+ class MyJob < ActiveJob::Base
167
+ unique :until_executed
168
+
169
+ def perform(foo, bar, baz)
170
+ # work
171
+ end
172
+
173
+ def lock_key
174
+ 'qux' # completely custom lock key
175
+ end
176
+
177
+ def runtime_lock_key
178
+ 'quux' # completely custom runtime lock key for :until_and_while_executing
179
+ end
180
+ end
181
+ ```
182
+
183
+ ### Unlock jobs manually
184
+
185
+ The selected strategy automatically unlocks jobs, but in some cases (e.g. the queue is purged) it is handy to unlock jobs manually.
186
+
187
+ ```ruby
188
+ # Remove the lock for particular arguments:
189
+ MyJob.unlock!(foo: 'bar')
190
+ # or
191
+ ActiveJob::Uniqueness.unlock!(job_class_name: 'MyJob', arguments: [{foo: 'bar'}])
192
+
193
+ # Remove all locks of MyJob
194
+ MyJob.unlock!
195
+ # or
196
+ ActiveJob::Uniqueness.unlock!(job_class_name: 'MyJob')
197
+
198
+ # Remove all locks
199
+ ActiveJob::Uniqueness.unlock!
200
+ ```
201
+
202
+ ## Test mode
203
+
204
+ Most probably you don't want jobs to be locked in tests. Add this line to your test suite (`rails_helper.rb`):
205
+
206
+ ```ruby
207
+ ActiveJob::Uniqueness.test_mode!
208
+ ```
209
+
210
+ ## Logging
211
+
212
+ ActiveJob::Uniqueness instruments `ActiveSupport::Notifications` with next events:
213
+ * `lock.active_job_uniqueness`
214
+ * `runtime_lock.active_job_uniqueness`
215
+ * `unlock.active_job_uniqueness`
216
+ * `runtime_unlock.active_job_uniqueness`
217
+ * `conflict.active_job_uniqueness`
218
+ * `runtime_conflict.active_job_uniqueness`
219
+
220
+ And then writes to `ActiveJob::Base.logger`.
221
+
222
+ **ActiveJob prior to version `6.1` will always log `Enqueued MyJob (Job ID) ...` even if the callback chain is halted. [Details](https://github.com/rails/rails/pull/37830)**
223
+
224
+ ## Testing
225
+
226
+ Run redis server (in separate console):
227
+ ```
228
+ docker run --rm -p 6379:6379 redis
229
+ ```
230
+
231
+ Run tests with:
232
+
233
+ ```sh
234
+ bundle
235
+ rake
236
+ ```
237
+
238
+ ## Sidekiq API support
239
+
240
+ ActiveJob::Uniqueness supports Sidekiq API to unset job locks on queues cleanup (e.g. via Sidekiq Web UI). Starting Sidekiq 5.1 job death also triggers locks cleanup.
241
+ Take into account that **[big queues cleanup becomes much slower](https://github.com/nordinvestments/activejob-uniqueness/issues/16)** because each job is being unlocked individually. In order to activate Sidekiq API patch require it explicitly in your Gemfile:
242
+
243
+ ```ruby
244
+ gem 'activejob-unique', require: 'active_job/uniqueness/sidekiq_patch'
245
+ ```
246
+
247
+ ## Why this fork?
248
+
249
+ The [original gem](https://github.com/veeqo/activejob-uniqueness) by Veeqo is excellent, but maintenance has slowed. This fork aims to:
250
+
251
+ - Keep up with new Rails and Ruby versions
252
+ - Merge useful PRs from the community
253
+ - Provide timely security updates
254
+ - Maintain backwards compatibility
255
+
256
+ We're grateful to the original authors and contributors. This fork preserves the MIT license and full attribution.
257
+
258
+ ## Contributing
259
+
260
+ Bug reports and pull requests are welcome on GitHub at https://github.com/nordinvestments/activejob-uniqueness.
261
+
262
+ ## License
263
+
264
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
265
+
266
+ Originally created by [Veeqo](https://www.veeqo.com). Maintained by [Nord Investments](https://github.com/nordinvestments).
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Uniqueness
5
+ # Provides ability to make ActiveJob job unique.
6
+ #
7
+ # For example:
8
+ #
9
+ # class FooJob < ActiveJob::Base
10
+ # queue_as :foo
11
+ #
12
+ # unique :until_executed, lock_ttl: 3.hours
13
+ #
14
+ # def perform(params)
15
+ # #...
16
+ # end
17
+ # end
18
+ #
19
+ module ActiveJobPatch
20
+ extend ActiveSupport::Concern
21
+
22
+ class_methods do
23
+ # Enables the uniqueness strategy for the job
24
+ # Params:
25
+ # +strategy+:: the uniqueness strategy.
26
+ # +options+:: uniqueness strategy options. For example: lock_ttl.
27
+ def unique(strategy, options = {})
28
+ validate_on_conflict_action!(options[:on_conflict])
29
+ validate_on_conflict_action!(options[:on_runtime_conflict])
30
+ validate_on_redis_connection_error!(options[:on_redis_connection_error])
31
+
32
+ self.lock_strategy_class = ActiveJob::Uniqueness::Strategies.lookup(strategy)
33
+ self.lock_options = options
34
+ end
35
+
36
+ # Unlocks all jobs of the job class if no arguments given
37
+ # Unlocks particular job if job arguments given
38
+ def unlock!(*arguments)
39
+ ActiveJob::Uniqueness.unlock!(job_class_name: name, arguments: arguments)
40
+ end
41
+
42
+ private
43
+
44
+ delegate :validate_on_conflict_action!,
45
+ :validate_on_redis_connection_error!,
46
+ to: :'ActiveJob::Uniqueness.config'
47
+ end
48
+
49
+ included do
50
+ class_attribute :lock_strategy_class, instance_writer: false
51
+ class_attribute :lock_options, instance_writer: false
52
+
53
+ before_enqueue { |job| job.lock_strategy.before_enqueue if job.lock_strategy_class }
54
+ before_perform { |job| job.lock_strategy.before_perform if job.lock_strategy_class }
55
+ after_perform { |job| job.lock_strategy.after_perform if job.lock_strategy_class }
56
+ around_enqueue { |job, block| job.lock_strategy_class ? job.lock_strategy.around_enqueue(block) : block.call }
57
+ around_perform { |job, block| job.lock_strategy_class ? job.lock_strategy.around_perform(block) : block.call }
58
+ end
59
+
60
+ def lock_strategy
61
+ @lock_strategy ||= lock_strategy_class.new(job: self)
62
+ end
63
+
64
+ # Override in your job class if you want to customize arguments set for a digest.
65
+ def lock_key_arguments
66
+ arguments
67
+ end
68
+
69
+ # Override lock_key method in your job class if you want to build completely custom lock key.
70
+ delegate :lock_key, :runtime_lock_key, to: :lock_key_generator
71
+
72
+ def lock_key_generator
73
+ @lock_key_generator ||= ActiveJob::Uniqueness::LockKey.new job_class_name: self.class.name,
74
+ arguments: lock_key_arguments
75
+ end
76
+ end
77
+
78
+ ActiveSupport.on_load(:active_job) do
79
+ ActiveJob::Base.include ActiveJob::Uniqueness::ActiveJobPatch
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Uniqueness
5
+ # Use /config/initializer/activejob_uniqueness.rb to configure ActiveJob::Uniqueness
6
+ #
7
+ # ActiveJob::Uniqueness.configure do |c|
8
+ # c.lock_ttl = 3.hours
9
+ # end
10
+ #
11
+ class Configuration
12
+ class_attribute :lock_ttl, default: 86_400 # 1.day
13
+ class_attribute :lock_prefix, default: 'activejob_uniqueness'
14
+ class_attribute :_on_conflict, default: :raise
15
+ class_attribute :_on_redis_connection_error, default: :raise
16
+ class_attribute :redlock_servers, default: [ENV.fetch('REDIS_URL', 'redis://localhost:6379')]
17
+ class_attribute :redlock_options, default: { retry_count: 0 }
18
+ class_attribute :lock_strategies, default: {}
19
+ class_attribute :digest_method
20
+
21
+ def on_conflict
22
+ _on_conflict
23
+ end
24
+
25
+ def on_conflict=(action)
26
+ validate_on_conflict_action!(action)
27
+
28
+ self._on_conflict = action
29
+ end
30
+
31
+ def on_redis_connection_error
32
+ _on_redis_connection_error
33
+ end
34
+
35
+ def on_redis_connection_error=(action)
36
+ validate_on_redis_connection_error!(action)
37
+
38
+ self._on_redis_connection_error = action
39
+ end
40
+
41
+ def validate_on_conflict_action!(action)
42
+ return if action.nil? || %i[log raise].include?(action) || action.respond_to?(:call)
43
+
44
+ raise ActiveJob::Uniqueness::InvalidOnConflictAction, "Unexpected '#{action}' action on conflict"
45
+ end
46
+
47
+ def validate_on_redis_connection_error!(action)
48
+ return if action.nil? || action == :raise || action.respond_to?(:call)
49
+
50
+ raise ActiveJob::Uniqueness::InvalidOnConflictAction, "Unexpected '#{action}' action on_redis_connection_error"
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ # Set default digest_method after class is defined
57
+ ActiveJob::Uniqueness::Configuration.digest_method = begin
58
+ require 'openssl'
59
+ OpenSSL::Digest::MD5
60
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ module Uniqueness
5
+ class Error < ::RuntimeError; end
6
+
7
+ # Raised when unknown strategy is referenced in the job class
8
+ #
9
+ # class MyJob < ActiveJob::Base
10
+ # unique :invalid_strategy # exception raised
11
+ # end
12
+ #
13
+ class StrategyNotFound < Error; end
14
+
15
+ # Raised on attempt to enqueue a not unique job with :raise on_conflict.
16
+ # Also raised when the runtime lock is taken by some other job.
17
+ #
18
+ # class MyJob < ActiveJob::Base
19
+ # unique :until_expired, on_conflict: :raise
20
+ # end
21
+ #
22
+ # MyJob.perform_later(1)
23
+ # MyJob.perform_later(1) # exception raised
24
+ #
25
+ class JobNotUnique < Error; end
26
+
27
+ # Raised when unsupported on_conflict action is used
28
+ #
29
+ # class MyJob < ActiveJob::Base
30
+ # unique :until_expired, on_conflict: :die # exception raised
31
+ # end
32
+ #
33
+ class InvalidOnConflictAction < Error; end
34
+ end
35
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_job/arguments'
4
+
5
+ module ActiveJob
6
+ module Uniqueness
7
+ class LockKey
8
+ FALLBACK_ARGUMENTS_STRING = 'no_arguments'
9
+
10
+ delegate :lock_prefix, :digest_method, to: :'ActiveJob::Uniqueness.config'
11
+
12
+ attr_reader :job_class_name, :arguments
13
+
14
+ def initialize(job_class_name: nil, arguments: nil)
15
+ if arguments.present? && job_class_name.blank?
16
+ raise ArgumentError, 'job_class_name is required if arguments given'
17
+ end
18
+
19
+ @job_class_name = job_class_name
20
+ @arguments = arguments || []
21
+ end
22
+
23
+ def lock_key
24
+ [
25
+ lock_prefix,
26
+ normalized_job_class_name,
27
+ arguments_key_part
28
+ ].join(':')
29
+ end
30
+
31
+ # used only by :until_and_while_executing strategy
32
+ def runtime_lock_key
33
+ [
34
+ lock_key,
35
+ 'runtime'
36
+ ].join(':')
37
+ end
38
+
39
+ def wildcard_key
40
+ [
41
+ lock_prefix,
42
+ normalized_job_class_name,
43
+ arguments.any? ? "#{arguments_key_part}*" : '*'
44
+ ].compact.join(':')
45
+ end
46
+
47
+ private
48
+
49
+ def arguments_key_part
50
+ arguments.any? ? arguments_digest : FALLBACK_ARGUMENTS_STRING
51
+ end
52
+
53
+ # ActiveJob::Arguments is used to reflect the way ActiveJob serializes arguments in order to
54
+ # serialize ActiveRecord models with GlobalID uuids instead of as_json which could give undesired artifacts
55
+ def serialized_arguments
56
+ ActiveSupport::JSON.encode(ActiveJob::Arguments.serialize(arguments))
57
+ end
58
+
59
+ def arguments_digest
60
+ digest_method.hexdigest(serialized_arguments)
61
+ end
62
+
63
+ def normalized_job_class_name
64
+ job_class_name&.underscore
65
+ end
66
+ end
67
+ end
68
+ end