activejob-uniqueness 0.1.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 +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.rubocop.yml +38 -0
- data/.travis.yml +21 -0
- data/CHANGELOG.md +1 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +102 -0
- data/Rakefile +8 -0
- data/activejob-uniqueness.gemspec +36 -0
- data/lib/active_job/uniqueness.rb +44 -0
- data/lib/active_job/uniqueness/configuration.rb +35 -0
- data/lib/active_job/uniqueness/errors.rb +35 -0
- data/lib/active_job/uniqueness/lock_key.rb +60 -0
- data/lib/active_job/uniqueness/lock_manager.rb +32 -0
- data/lib/active_job/uniqueness/log_subscriber.rb +101 -0
- data/lib/active_job/uniqueness/patch.rb +89 -0
- data/lib/active_job/uniqueness/strategies.rb +31 -0
- data/lib/active_job/uniqueness/strategies/base.rb +112 -0
- data/lib/active_job/uniqueness/strategies/until_and_while_executing.rb +43 -0
- data/lib/active_job/uniqueness/strategies/until_executed.rb +17 -0
- data/lib/active_job/uniqueness/strategies/until_executing.rb +17 -0
- data/lib/active_job/uniqueness/strategies/until_expired.rb +13 -0
- data/lib/active_job/uniqueness/strategies/while_executing.rb +26 -0
- data/lib/active_job/uniqueness/test_lock_manager.rb +16 -0
- data/lib/active_job/uniqueness/version.rb +7 -0
- data/lib/activejob/uniqueness.rb +3 -0
- data/lib/generators/active_job/uniqueness/install_generator.rb +16 -0
- data/lib/generators/active_job/uniqueness/templates/config/initializers/active_job_uniqueness.rb +40 -0
- metadata +165 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5fda42367a82ec4d28e8e9dd2aa4e4f1774b28133170aab20095a7cf1366ce6f
|
4
|
+
data.tar.gz: 4e29cd3caf6b85713a570fd95aa4272eec7df23f96a7fb44fb05574c83dc1d5d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6ca9eb292b99425c77f3f5d3a2082ba3e15d045c683123203667328bb0e24033a3139a5b465fffd1da6b80fea6f3e963544cef1bbde5107ecb666d92d82396e6
|
7
|
+
data.tar.gz: 8deceb5728a0459ac983a9e0ec7501abf9ba050e9536c626770aa568504ba05737bf1664907abdff312c83cfe3c97d4ea61df572eaee56cfb54dd88245afa51d
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
AllCops:
|
2
|
+
NewCops: enable
|
3
|
+
|
4
|
+
Layout/LineLength:
|
5
|
+
Max: 120
|
6
|
+
Exclude:
|
7
|
+
- spec/**/*
|
8
|
+
|
9
|
+
Layout/MultilineMethodCallIndentation:
|
10
|
+
Exclude:
|
11
|
+
- spec/**/*
|
12
|
+
|
13
|
+
Lint/AmbiguousBlockAssociation:
|
14
|
+
Exclude:
|
15
|
+
- spec/**/*
|
16
|
+
|
17
|
+
Metrics/AbcSize:
|
18
|
+
Exclude:
|
19
|
+
- spec/**/*
|
20
|
+
|
21
|
+
Metrics/BlockLength:
|
22
|
+
Exclude:
|
23
|
+
- activejob-uniqueness.gemspec
|
24
|
+
- spec/**/*
|
25
|
+
|
26
|
+
Metrics/MethodLength:
|
27
|
+
Exclude:
|
28
|
+
- spec/**/*
|
29
|
+
|
30
|
+
Style/ClassAndModuleChildren:
|
31
|
+
Exclude:
|
32
|
+
- spec/**/*
|
33
|
+
|
34
|
+
Style/Documentation:
|
35
|
+
Enabled: false
|
36
|
+
|
37
|
+
Style/RescueModifier:
|
38
|
+
Enabled: false
|
data/.travis.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
dist: xenial
|
3
|
+
language: ruby
|
4
|
+
services:
|
5
|
+
- redis-server
|
6
|
+
cache: bundler
|
7
|
+
rvm:
|
8
|
+
- 2.5.8
|
9
|
+
- 2.6.6
|
10
|
+
- 2.7.1
|
11
|
+
env:
|
12
|
+
- ACTIVEJOB_VERSION="~> 4.2.11"
|
13
|
+
- ACTIVEJOB_VERSION="~> 5.2.4"
|
14
|
+
- ACTIVEJOB_VERSION="~> 6.0.3"
|
15
|
+
jobs:
|
16
|
+
exclude:
|
17
|
+
- rvm: 2.7.1
|
18
|
+
env: ACTIVEJOB_VERSION="~> 4.2.11"
|
19
|
+
|
20
|
+
before_install: gem install bundler -v 1.17.3
|
21
|
+
bundler_args: --jobs 3 --retry 3
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
## Original Release: 0.1.0
|
data/Gemfile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
6
|
+
|
7
|
+
# Specify your gem's dependencies in activejob-uniqueness.gemspec
|
8
|
+
gemspec
|
9
|
+
|
10
|
+
gem 'activejob', ENV.fetch('ACTIVEJOB_VERSION', '~> 4.2.11')
|
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,102 @@
|
|
1
|
+
# Job uniqueness for ActiveJob
|
2
|
+
[](https://travis-ci.com/veeqo/activejob-uniqueness) [](https://badge.fury.io/rb/activejob-uniqueness)
|
3
|
+
|
4
|
+
The gem allows to protect job uniqueness with next strategies:
|
5
|
+
|
6
|
+
| Strategy | The job is locked | The job is unlocked |
|
7
|
+
|-|-|-|
|
8
|
+
| `until_executing` | when **pushed** to the queue | when **processing starts** |
|
9
|
+
| `until_executed` | when **pushed** to the queue | when the job is **processed successfully** |
|
10
|
+
| `until_expired` | when **pushed** to the queue | when the lock is **expired** |
|
11
|
+
| `until_and_while_executing` | when **pushed** to the queue | when **processing starts**<br>a runtime lock is acquired to **prevent simultaneous jobs** |
|
12
|
+
| `while_executing` | when **processing starts** | when the job is **processed**<br>with any result including an error |
|
13
|
+
|
14
|
+
Inspired by [SidekiqUniqueJobs](https://github.com/mhenrixon/sidekiq-unique-jobs), uses [Redlock](https://github.com/leandromoreira/redlock-rb) under the hood, sponsored by [Veeqo](https://www.veeqo.com/).
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add the "meta-tags" gem to your Gemfile.
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem 'activejob-uniqueness'
|
22
|
+
```
|
23
|
+
|
24
|
+
And run `bundle install` command.
|
25
|
+
|
26
|
+
## Configuration
|
27
|
+
|
28
|
+
ActiveJob::Uniqueness is ready to work without any configuration. It will use `REDIS_URL` to connect to Redis instance.
|
29
|
+
To override the defaults, create an initializer `config/initializers/active_job_uniqueness.rb` using the following command:
|
30
|
+
|
31
|
+
```sh
|
32
|
+
rails generate active_job:uniqueness:install
|
33
|
+
```
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
Define uniqueness strategy for your job via `unique` class method:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
class MyJob < ActiveJob::Base
|
41
|
+
unique :until_executed
|
42
|
+
|
43
|
+
# Custom expiration:
|
44
|
+
# unique :until_executed, lock_ttl: 3.hours
|
45
|
+
|
46
|
+
# Do not raise error on non unique jobs enqueuing:
|
47
|
+
# unique :until_executed, on_conflict: :log
|
48
|
+
|
49
|
+
# Handle conflict by custom Proc:
|
50
|
+
# unique :until_executed, on_conflict: ->(job) { job.logger.info 'Oops' }
|
51
|
+
|
52
|
+
# The :until_and_while_executing strategy supports extra attributes for a runtime lock:
|
53
|
+
# unique :until_and_while_executing runtime_lock_ttl: 10.minutes, on_runtime_conflict: :log
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
ActiveJob::Uniqueness allows to manually unlock jobs:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
# Remove the lock for particular arguments:
|
61
|
+
MyJob.unlock!(foo: 'bar')
|
62
|
+
# or
|
63
|
+
ActiveJob::Uniqueness.unlock!(job_class_name: 'MyJob', arguments: [{foo: 'bar'}])
|
64
|
+
|
65
|
+
# Remove all locks of MyJob
|
66
|
+
MyJob.unlock!
|
67
|
+
# or
|
68
|
+
ActiveJob::Uniqueness.unlock!(job_class_name: 'MyJob')
|
69
|
+
|
70
|
+
# Remove all locks
|
71
|
+
ActiveJob::Uniqueness.unlock!
|
72
|
+
```
|
73
|
+
|
74
|
+
## Test mode
|
75
|
+
|
76
|
+
Most probably you don't want jobs to be locked in tests. Add this line to your test suite (`rails_helper.rb`):
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
ActiveJob::Uniqueness.test_mode!
|
80
|
+
```
|
81
|
+
|
82
|
+
## Logging
|
83
|
+
|
84
|
+
ActiveJob::Uniqueness instruments `ActiveSupport::Notifications` with next events:
|
85
|
+
* `lock.active_job_uniqueness`
|
86
|
+
* `runtime_lock.active_job_uniqueness`
|
87
|
+
* `unlock.active_job_uniqueness`
|
88
|
+
* `runtime_unlock.active_job_uniqueness`
|
89
|
+
* `conflict.active_job_uniqueness`
|
90
|
+
* `runtime_conflict.active_job_uniqueness`
|
91
|
+
|
92
|
+
And then writes to `ActiveJob::Base.logger`.
|
93
|
+
|
94
|
+
### ActiveJob prior to version `6.1` will always log `Enqueued MyJob (Job ID) ...` even if the callback chain was halted. [Details](https://github.com/rails/rails/pull/37830)
|
95
|
+
|
96
|
+
## Contributing
|
97
|
+
|
98
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/veeqo/activejob-uniqueness.
|
99
|
+
|
100
|
+
## License
|
101
|
+
|
102
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'active_job/uniqueness/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'activejob-uniqueness'
|
9
|
+
spec.version = ActiveJob::Uniqueness::VERSION
|
10
|
+
spec.authors = ['Rustam Sharshenov']
|
11
|
+
spec.email = ['rustam@sharshenov.com']
|
12
|
+
|
13
|
+
spec.summary = 'Ensure uniqueness of your ActiveJob jobs'
|
14
|
+
spec.description = 'Ensure uniqueness of your ActiveJob jobs'
|
15
|
+
spec.homepage = 'https://github.com/veeqo/activejob-uniqueness'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
20
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
21
|
+
spec.metadata['changelog_uri'] = 'https://github.com/veeqo/activejob-uniqueness/blob/master/CHANGELOG.md'
|
22
|
+
end
|
23
|
+
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|\.rubocop.yml)/}) }
|
26
|
+
end
|
27
|
+
spec.require_paths = ['lib']
|
28
|
+
|
29
|
+
spec.add_dependency 'activejob', '>= 4.2'
|
30
|
+
spec.add_dependency 'redlock', '>= 1.2', '< 2'
|
31
|
+
|
32
|
+
spec.add_development_dependency 'bundler'
|
33
|
+
spec.add_development_dependency 'pry-byebug'
|
34
|
+
spec.add_development_dependency 'rake'
|
35
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
36
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext'
|
5
|
+
require 'redlock'
|
6
|
+
require 'openssl'
|
7
|
+
require 'active_job/uniqueness/version'
|
8
|
+
require 'active_job/uniqueness/errors'
|
9
|
+
require 'active_job/uniqueness/log_subscriber'
|
10
|
+
require 'active_job/uniqueness/patch'
|
11
|
+
|
12
|
+
module ActiveJob
|
13
|
+
module Uniqueness
|
14
|
+
extend ActiveSupport::Autoload
|
15
|
+
|
16
|
+
autoload :Configuration
|
17
|
+
autoload :LockKey
|
18
|
+
autoload :Strategies
|
19
|
+
autoload :LockManager
|
20
|
+
autoload :TestLockManager
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def configure
|
24
|
+
yield config
|
25
|
+
end
|
26
|
+
|
27
|
+
def config
|
28
|
+
@config ||= ActiveJob::Uniqueness::Configuration.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def lock_manager
|
32
|
+
@lock_manager ||= ActiveJob::Uniqueness::LockManager.new(config.redlock_servers, config.redlock_options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def unlock!(*args)
|
36
|
+
lock_manager.delete_locks(ActiveJob::Uniqueness::LockKey.new(*args).wildcard_key)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_mode!
|
40
|
+
@lock_manager = ActiveJob::Uniqueness::TestLockManager.new
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,35 @@
|
|
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
|
+
include ActiveSupport::Configurable
|
13
|
+
|
14
|
+
config_accessor(:lock_ttl) { 1.day }
|
15
|
+
config_accessor(:lock_prefix) { 'activejob_uniqueness' }
|
16
|
+
config_accessor(:on_conflict) { :raise }
|
17
|
+
config_accessor(:digest_method) { OpenSSL::Digest::MD5 }
|
18
|
+
config_accessor(:redlock_servers) { [ENV.fetch('REDIS_URL', 'redis://localhost:6379')] }
|
19
|
+
config_accessor(:redlock_options) { {} }
|
20
|
+
config_accessor(:lock_strategies) { {} }
|
21
|
+
|
22
|
+
def on_conflict=(action)
|
23
|
+
validate_on_conflict_action!(action)
|
24
|
+
|
25
|
+
config.on_conflict = action
|
26
|
+
end
|
27
|
+
|
28
|
+
def validate_on_conflict_action!(action)
|
29
|
+
return if action.nil? || %i[log raise].include?(action) || action.respond_to?(:call)
|
30
|
+
|
31
|
+
raise ActiveJob::Uniqueness::InvalidOnConflictAction, "Unexpected '#{action}' action on conflict"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
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,60 @@
|
|
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
|
+
def wildcard_key
|
32
|
+
[
|
33
|
+
lock_prefix,
|
34
|
+
normalized_job_class_name,
|
35
|
+
arguments.any? ? arguments_key_part + '*' : '*'
|
36
|
+
].compact.join(':')
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def arguments_key_part
|
42
|
+
arguments.any? ? arguments_digest : FALLBACK_ARGUMENTS_STRING
|
43
|
+
end
|
44
|
+
|
45
|
+
# ActiveJob::Arguments is used to reflect the way ActiveJob serializes arguments in order to
|
46
|
+
# serialize ActiveRecord models with GlobalID uuids instead of as_json which could give undesired artifacts
|
47
|
+
def serialized_arguments
|
48
|
+
ActiveSupport::JSON.encode(ActiveJob::Arguments.serialize(arguments))
|
49
|
+
end
|
50
|
+
|
51
|
+
def arguments_digest
|
52
|
+
digest_method.hexdigest(serialized_arguments)
|
53
|
+
end
|
54
|
+
|
55
|
+
def normalized_job_class_name
|
56
|
+
job_class_name&.underscore
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Uniqueness
|
5
|
+
# Redlock requires a value of the lock to release the resource by Redlock::Client#unlock method.
|
6
|
+
# LockManager introduces LockManager#delete_lock to unlock by resource key only.
|
7
|
+
# See https://github.com/leandromoreira/redlock-rb/issues/51 for more details.
|
8
|
+
class LockManager < ::Redlock::Client
|
9
|
+
# Unlocks a resource by resource only.
|
10
|
+
def delete_lock(resource)
|
11
|
+
@servers.each do |server|
|
12
|
+
server.instance_variable_get(:'@redis').with do |conn|
|
13
|
+
conn.del resource
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
# Unlocks multiple resources by key wildcard.
|
21
|
+
def delete_locks(wildcard)
|
22
|
+
@servers.each do |server|
|
23
|
+
server.instance_variable_get(:'@redis').with do |conn|
|
24
|
+
conn.scan_each(match: wildcard).each { |key| conn.del key }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/log_subscriber'
|
4
|
+
|
5
|
+
module ActiveJob
|
6
|
+
class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
|
7
|
+
def lock(event)
|
8
|
+
job = event.payload[:job]
|
9
|
+
resource = event.payload[:resource]
|
10
|
+
|
11
|
+
debug do
|
12
|
+
"Locked #{lock_info(job, resource)}" + args_info(job)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def runtime_lock(event)
|
17
|
+
job = event.payload[:job]
|
18
|
+
resource = event.payload[:resource]
|
19
|
+
|
20
|
+
debug do
|
21
|
+
"Locked runtime #{lock_info(job, resource)}" + args_info(job)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def unlock(event)
|
26
|
+
job = event.payload[:job]
|
27
|
+
resource = event.payload[:resource]
|
28
|
+
|
29
|
+
debug do
|
30
|
+
"Unlocked #{lock_info(job, resource)}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def runtime_unlock(event)
|
35
|
+
job = event.payload[:job]
|
36
|
+
resource = event.payload[:resource]
|
37
|
+
|
38
|
+
debug do
|
39
|
+
"Unlocked runtime #{lock_info(job, resource)}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def conflict(event)
|
44
|
+
job = event.payload[:job]
|
45
|
+
resource = event.payload[:resource]
|
46
|
+
|
47
|
+
info do
|
48
|
+
"Not unique #{lock_info(job, resource)}" + args_info(job)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def runtime_conflict(event)
|
53
|
+
job = event.payload[:job]
|
54
|
+
resource = event.payload[:resource]
|
55
|
+
|
56
|
+
info do
|
57
|
+
"Not unique runtime #{lock_info(job, resource)}" + args_info(job)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def lock_info(job, resource)
|
64
|
+
"#{job.class.name} (Job ID: #{job.job_id}) (Lock key: #{resource})"
|
65
|
+
end
|
66
|
+
|
67
|
+
def args_info(job)
|
68
|
+
if job.arguments.any? && log_arguments?(job)
|
69
|
+
' with arguments: ' +
|
70
|
+
job.arguments.map { |arg| format(arg).inspect }.join(', ')
|
71
|
+
else
|
72
|
+
''
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def log_arguments?(job)
|
77
|
+
return true unless job.class.respond_to?(:log_arguments?)
|
78
|
+
|
79
|
+
job.class.log_arguments?
|
80
|
+
end
|
81
|
+
|
82
|
+
def format(arg)
|
83
|
+
case arg
|
84
|
+
when Hash
|
85
|
+
arg.transform_values { |value| format(value) }
|
86
|
+
when Array
|
87
|
+
arg.map { |value| format(value) }
|
88
|
+
when GlobalID::Identification
|
89
|
+
arg.to_global_id rescue arg
|
90
|
+
else
|
91
|
+
arg
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def logger
|
96
|
+
ActiveJob::Base.logger
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
ActiveJob::LogSubscriber.attach_to :active_job_uniqueness
|
@@ -0,0 +1,89 @@
|
|
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 Patch
|
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
|
+
|
31
|
+
self.lock_strategy_class = ActiveJob::Uniqueness::Strategies.lookup(strategy)
|
32
|
+
self.lock_options = options
|
33
|
+
end
|
34
|
+
|
35
|
+
# Unlocks all jobs of the job class if no arguments given
|
36
|
+
# Unlocks particular job if job arguments given
|
37
|
+
def unlock!(*arguments)
|
38
|
+
ActiveJob::Uniqueness.unlock!(job_class_name: name, arguments: arguments)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
delegate :validate_on_conflict_action!, to: :'ActiveJob::Uniqueness.config'
|
44
|
+
end
|
45
|
+
|
46
|
+
included do
|
47
|
+
class_attribute :lock_strategy_class, instance_writer: false
|
48
|
+
class_attribute :lock_options, instance_writer: false
|
49
|
+
|
50
|
+
before_enqueue { |job| job.lock_strategy.before_enqueue if job.lock_strategy_class }
|
51
|
+
before_perform { |job| job.lock_strategy.before_perform if job.lock_strategy_class }
|
52
|
+
after_perform { |job| job.lock_strategy.after_perform if job.lock_strategy_class }
|
53
|
+
around_enqueue { |job, block| job.lock_strategy_class ? job.lock_strategy.around_enqueue(block) : block.call }
|
54
|
+
around_perform { |job, block| job.lock_strategy_class ? job.lock_strategy.around_perform(block) : block.call }
|
55
|
+
end
|
56
|
+
|
57
|
+
def lock_strategy
|
58
|
+
@lock_strategy ||= lock_strategy_class.new(lock_options.merge(lock_key: lock_key, job: self))
|
59
|
+
end
|
60
|
+
|
61
|
+
# Override in your job class if you want to customize arguments set for a digest.
|
62
|
+
def lock_key_arguments
|
63
|
+
arguments
|
64
|
+
end
|
65
|
+
|
66
|
+
# Override lock_key method in your job class if you want to build completely custom lock key.
|
67
|
+
delegate :lock_key, to: :lock_key_generator
|
68
|
+
|
69
|
+
def lock_key_generator
|
70
|
+
ActiveJob::Uniqueness::LockKey.new job_class_name: self.class.name,
|
71
|
+
arguments: lock_key_arguments
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
if defined?(Rails)
|
76
|
+
class Railtie < Rails::Railtie
|
77
|
+
initializer 'active_job_uniqueness.patch_active_job' do
|
78
|
+
ActiveSupport.on_load(:active_job) do
|
79
|
+
ActiveJob::Base.include ActiveJob::Uniqueness::Patch
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
else
|
84
|
+
ActiveSupport.on_load(:active_job) do
|
85
|
+
ActiveJob::Base.include ActiveJob::Uniqueness::Patch
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Uniqueness
|
5
|
+
# See Configuration#lock_strategies if you want to define custom strategy
|
6
|
+
module Strategies
|
7
|
+
extend ActiveSupport::Autoload
|
8
|
+
|
9
|
+
autoload :Base
|
10
|
+
autoload :UntilExpired
|
11
|
+
autoload :UntilExecuted
|
12
|
+
autoload :UntilExecuting
|
13
|
+
autoload :UntilAndWhileExecuting
|
14
|
+
autoload :WhileExecuting
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def lookup(strategy)
|
18
|
+
matching_strategy(strategy.to_s.camelize) ||
|
19
|
+
ActiveJob::Uniqueness.config.lock_strategies[strategy] ||
|
20
|
+
raise(StrategyNotFound, "Strategy '#{strategy}' is not found. Is it declared in the configuration?")
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def matching_strategy(const)
|
26
|
+
const_get(const, false) if const_defined?(const, false)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Uniqueness
|
5
|
+
module Strategies
|
6
|
+
# Base strategy is not supposed to actually be used as uniqueness strategy.
|
7
|
+
class Base
|
8
|
+
# https://github.com/rails/rails/pull/17227
|
9
|
+
# https://groups.google.com/g/rubyonrails-core/c/mhD4T90g0G4
|
10
|
+
ACTIVEJOB_SUPPORTS_THROW_ABORT = ActiveJob.gem_version >= Gem::Version.new('5.0')
|
11
|
+
|
12
|
+
delegate :lock_manager, :config, to: :'ActiveJob::Uniqueness'
|
13
|
+
|
14
|
+
attr_reader :lock_key, :lock_ttl, :on_conflict, :job
|
15
|
+
|
16
|
+
def initialize(lock_key:, lock_ttl: nil, on_conflict: nil, job: nil)
|
17
|
+
@lock_key = lock_key
|
18
|
+
@lock_ttl = (lock_ttl || config.lock_ttl).to_i * 1000 # ms
|
19
|
+
@on_conflict = on_conflict || config.on_conflict
|
20
|
+
@job = job
|
21
|
+
end
|
22
|
+
|
23
|
+
def lock(resource:, ttl:, event: :lock)
|
24
|
+
lock_manager.lock(resource, ttl).tap do |result|
|
25
|
+
instrument(event, resource: resource, ttl: ttl) if result
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def unlock(resource:, event: :unlock)
|
30
|
+
lock_manager.delete_lock(resource).tap do
|
31
|
+
instrument(event, resource: resource)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def before_enqueue
|
36
|
+
# Expected to be overriden in the descendant strategy
|
37
|
+
end
|
38
|
+
|
39
|
+
def before_perform
|
40
|
+
# Expected to be overriden in the descendant strategy
|
41
|
+
end
|
42
|
+
|
43
|
+
def around_enqueue(block)
|
44
|
+
# Expected to be overriden in the descendant strategy
|
45
|
+
block.call
|
46
|
+
end
|
47
|
+
|
48
|
+
def around_perform(block)
|
49
|
+
# Expected to be overriden in the descendant strategy
|
50
|
+
block.call
|
51
|
+
end
|
52
|
+
|
53
|
+
def after_perform; end
|
54
|
+
|
55
|
+
module LockingOnEnqueue
|
56
|
+
def before_enqueue
|
57
|
+
return if lock(resource: lock_key, ttl: lock_ttl)
|
58
|
+
|
59
|
+
handle_conflict(resource: lock_key, on_conflict: on_conflict)
|
60
|
+
abort_job
|
61
|
+
end
|
62
|
+
|
63
|
+
def around_enqueue(block)
|
64
|
+
return if @job_aborted # ActiveJob 4.2 workaround
|
65
|
+
|
66
|
+
enqueued = false
|
67
|
+
|
68
|
+
block.call
|
69
|
+
|
70
|
+
enqueued = true
|
71
|
+
ensure
|
72
|
+
unlock(resource: lock_key) unless @job_aborted || enqueued
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def handle_conflict(on_conflict:, resource:, event: :conflict)
|
79
|
+
case on_conflict
|
80
|
+
when :log then instrument(event, resource: resource)
|
81
|
+
when :raise then raise_not_unique_job_error(resource: resource, event: event)
|
82
|
+
else
|
83
|
+
on_conflict.call(job)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def abort_job
|
88
|
+
@job_aborted = true # ActiveJob 4.2 workaround
|
89
|
+
|
90
|
+
ACTIVEJOB_SUPPORTS_THROW_ABORT ? throw(:abort) : false
|
91
|
+
end
|
92
|
+
|
93
|
+
def instrument(action, payload = {})
|
94
|
+
ActiveSupport::Notifications.instrument "#{action}.active_job_uniqueness", payload.merge(job: job)
|
95
|
+
end
|
96
|
+
|
97
|
+
def raise_not_unique_job_error(resource:, event:)
|
98
|
+
message = [
|
99
|
+
job.class.name,
|
100
|
+
"(Job ID: #{job.job_id})",
|
101
|
+
"(Lock key: #{resource})",
|
102
|
+
job.arguments.inspect
|
103
|
+
]
|
104
|
+
|
105
|
+
message.unshift(event == :runtime_conflict ? 'Not unique runtime' : 'Not unique')
|
106
|
+
|
107
|
+
raise JobNotUnique, message.join(' ')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Uniqueness
|
5
|
+
module Strategies
|
6
|
+
# Locks the job when it is pushed to the queue.
|
7
|
+
# Unlocks the job before the job is started.
|
8
|
+
# Then creates runtime lock to prevent simultaneous jobs from being executed.
|
9
|
+
class UntilAndWhileExecuting < Base
|
10
|
+
include LockingOnEnqueue
|
11
|
+
|
12
|
+
attr_reader :runtime_lock_ttl, :on_runtime_conflict
|
13
|
+
|
14
|
+
def initialize(runtime_lock_ttl: nil, on_runtime_conflict: nil, **params)
|
15
|
+
super(params)
|
16
|
+
@runtime_lock_ttl = runtime_lock_ttl.present? ? runtime_lock_ttl.to_i * 1000 : lock_ttl
|
17
|
+
@on_runtime_conflict = on_runtime_conflict || on_conflict
|
18
|
+
end
|
19
|
+
|
20
|
+
def before_perform
|
21
|
+
unlock(resource: lock_key)
|
22
|
+
|
23
|
+
return if lock(resource: runtime_lock_key, ttl: runtime_lock_ttl, event: :runtime_lock)
|
24
|
+
|
25
|
+
handle_conflict(on_conflict: on_runtime_conflict, resource: runtime_lock_key, event: :runtime_conflict)
|
26
|
+
abort_job
|
27
|
+
end
|
28
|
+
|
29
|
+
def around_perform(block)
|
30
|
+
return if @job_aborted # ActiveJob 4.2 workaround
|
31
|
+
|
32
|
+
block.call
|
33
|
+
ensure
|
34
|
+
unlock(resource: runtime_lock_key, event: :runtime_unlock) unless @job_aborted
|
35
|
+
end
|
36
|
+
|
37
|
+
def runtime_lock_key
|
38
|
+
[lock_key, 'runtime'].join(':')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Uniqueness
|
5
|
+
module Strategies
|
6
|
+
# Locks the job when it is pushed to the queue.
|
7
|
+
# Unlocks the job when the job is finished.
|
8
|
+
class UntilExecuted < Base
|
9
|
+
include LockingOnEnqueue
|
10
|
+
|
11
|
+
def after_perform
|
12
|
+
unlock(resource: lock_key)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Uniqueness
|
5
|
+
module Strategies
|
6
|
+
# Locks the job when it is pushed to the queue.
|
7
|
+
# Unlocks the job before the job is started.
|
8
|
+
class UntilExecuting < Base
|
9
|
+
include LockingOnEnqueue
|
10
|
+
|
11
|
+
def before_perform
|
12
|
+
unlock(resource: lock_key)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Uniqueness
|
5
|
+
module Strategies
|
6
|
+
# Locks the job when it is pushed to the queue.
|
7
|
+
# Does not allow new jobs enqueued until lock is expired.
|
8
|
+
class UntilExpired < Base
|
9
|
+
include LockingOnEnqueue
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Uniqueness
|
5
|
+
module Strategies
|
6
|
+
# Locks the job when the job starts.
|
7
|
+
# Unlocks the job when the job is finished.
|
8
|
+
class WhileExecuting < Base
|
9
|
+
def before_perform
|
10
|
+
return if lock(resource: lock_key, ttl: lock_ttl, event: :runtime_lock)
|
11
|
+
|
12
|
+
handle_conflict(resource: lock_key, event: :runtime_conflict, on_conflict: on_conflict)
|
13
|
+
abort_job
|
14
|
+
end
|
15
|
+
|
16
|
+
def around_perform(block)
|
17
|
+
return if @job_aborted # ActiveJob 4.2 workaround
|
18
|
+
|
19
|
+
block.call
|
20
|
+
ensure
|
21
|
+
unlock(resource: lock_key, event: :runtime_unlock) unless @job_aborted
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Uniqueness
|
5
|
+
# Mocks ActiveJob::Uniqueness::LockManager methods.
|
6
|
+
# See ActiveJob::Uniqueness.test_mode!
|
7
|
+
class TestLockManager
|
8
|
+
def lock(*_args)
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
alias delete_lock lock
|
13
|
+
alias delete_locks lock
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module Uniqueness
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
desc 'Copy ActiveJob::Uniqueness default files'
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
9
|
+
|
10
|
+
def copy_config
|
11
|
+
template 'config/initializers/active_job_uniqueness.rb'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/generators/active_job/uniqueness/templates/config/initializers/active_job_uniqueness.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ActiveJob::Uniqueness.configure do |config|
|
4
|
+
# Global default expiration for lock keys. Each job can define its own ttl via :lock_ttl option.
|
5
|
+
# Stategy :until_and_while_executing also accept :on_runtime_ttl option.
|
6
|
+
#
|
7
|
+
# config.lock_ttl = 1.day
|
8
|
+
|
9
|
+
# Prefix for lock keys. Can not be set per job.
|
10
|
+
#
|
11
|
+
# config.lock_prefix = 'activejob_uniqueness'
|
12
|
+
|
13
|
+
# Default action on lock conflict. Can be set per job.
|
14
|
+
# Stategy :until_and_while_executing also accept :on_runtime_conflict option.
|
15
|
+
# Allowed values are
|
16
|
+
# :raise - raises ActiveJob::Uniqueness::JobNotUnique
|
17
|
+
# :log - instruments ActiveSupport::Notifications and logs event to the ActiveJob::Logger
|
18
|
+
# proc - custom Proc. For example, ->(job) { job.logger.info('Oops') }
|
19
|
+
#
|
20
|
+
# config.on_conflict = :raise
|
21
|
+
|
22
|
+
# Digest method for lock keys generating. Expected to have `hexdigest` class method.
|
23
|
+
#
|
24
|
+
# config.digest_method = OpenSSL::Digest::MD5
|
25
|
+
|
26
|
+
# Array of redis servers for Redlock quorum.
|
27
|
+
# Read more at https://github.com/leandromoreira/redlock-rb#redis-client-configuration
|
28
|
+
#
|
29
|
+
# config.redlock_servers = [ENV.fetch('REDIS_URL', 'redis://localhost:6379')]
|
30
|
+
|
31
|
+
# Custom options for Redlock.
|
32
|
+
# Read more at https://github.com/leandromoreira/redlock-rb#redlock-configuration
|
33
|
+
#
|
34
|
+
# config.redlock_options = {}
|
35
|
+
|
36
|
+
# Custom strategies.
|
37
|
+
# config.lock_strategies = { my_strategy: MyStrategy }
|
38
|
+
#
|
39
|
+
# config.lock_strategies = {}
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activejob-uniqueness
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rustam Sharshenov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-07-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activejob
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: redlock
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.2'
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '2'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1.2'
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bundler
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: pry-byebug
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rake
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rspec
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '3.0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3.0'
|
103
|
+
description: Ensure uniqueness of your ActiveJob jobs
|
104
|
+
email:
|
105
|
+
- rustam@sharshenov.com
|
106
|
+
executables: []
|
107
|
+
extensions: []
|
108
|
+
extra_rdoc_files: []
|
109
|
+
files:
|
110
|
+
- ".gitignore"
|
111
|
+
- ".rspec"
|
112
|
+
- ".rubocop.yml"
|
113
|
+
- ".travis.yml"
|
114
|
+
- CHANGELOG.md
|
115
|
+
- Gemfile
|
116
|
+
- LICENSE.txt
|
117
|
+
- README.md
|
118
|
+
- Rakefile
|
119
|
+
- activejob-uniqueness.gemspec
|
120
|
+
- lib/active_job/uniqueness.rb
|
121
|
+
- lib/active_job/uniqueness/configuration.rb
|
122
|
+
- lib/active_job/uniqueness/errors.rb
|
123
|
+
- lib/active_job/uniqueness/lock_key.rb
|
124
|
+
- lib/active_job/uniqueness/lock_manager.rb
|
125
|
+
- lib/active_job/uniqueness/log_subscriber.rb
|
126
|
+
- lib/active_job/uniqueness/patch.rb
|
127
|
+
- lib/active_job/uniqueness/strategies.rb
|
128
|
+
- lib/active_job/uniqueness/strategies/base.rb
|
129
|
+
- lib/active_job/uniqueness/strategies/until_and_while_executing.rb
|
130
|
+
- lib/active_job/uniqueness/strategies/until_executed.rb
|
131
|
+
- lib/active_job/uniqueness/strategies/until_executing.rb
|
132
|
+
- lib/active_job/uniqueness/strategies/until_expired.rb
|
133
|
+
- lib/active_job/uniqueness/strategies/while_executing.rb
|
134
|
+
- lib/active_job/uniqueness/test_lock_manager.rb
|
135
|
+
- lib/active_job/uniqueness/version.rb
|
136
|
+
- lib/activejob/uniqueness.rb
|
137
|
+
- lib/generators/active_job/uniqueness/install_generator.rb
|
138
|
+
- lib/generators/active_job/uniqueness/templates/config/initializers/active_job_uniqueness.rb
|
139
|
+
homepage: https://github.com/veeqo/activejob-uniqueness
|
140
|
+
licenses:
|
141
|
+
- MIT
|
142
|
+
metadata:
|
143
|
+
homepage_uri: https://github.com/veeqo/activejob-uniqueness
|
144
|
+
source_code_uri: https://github.com/veeqo/activejob-uniqueness
|
145
|
+
changelog_uri: https://github.com/veeqo/activejob-uniqueness/blob/master/CHANGELOG.md
|
146
|
+
post_install_message:
|
147
|
+
rdoc_options: []
|
148
|
+
require_paths:
|
149
|
+
- lib
|
150
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
requirements: []
|
161
|
+
rubygems_version: 3.0.6
|
162
|
+
signing_key:
|
163
|
+
specification_version: 4
|
164
|
+
summary: Ensure uniqueness of your ActiveJob jobs
|
165
|
+
test_files: []
|