simple_mutex 1.0.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/.github/worklows/ci.yml +33 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +14 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +113 -0
- data/LICENSE +21 -0
- data/README.md +364 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/simple_mutex/base_cleaner.rb +97 -0
- data/lib/simple_mutex/helper.rb +83 -0
- data/lib/simple_mutex/mutex.rb +177 -0
- data/lib/simple_mutex/sidekiq_support/batch.rb +78 -0
- data/lib/simple_mutex/sidekiq_support/batch_callbacks.rb +15 -0
- data/lib/simple_mutex/sidekiq_support/batch_cleaner.rb +32 -0
- data/lib/simple_mutex/sidekiq_support/job_cleaner.rb +27 -0
- data/lib/simple_mutex/sidekiq_support/job_mixin.rb +65 -0
- data/lib/simple_mutex/sidekiq_support/job_wrapper.rb +52 -0
- data/lib/simple_mutex/version.rb +5 -0
- data/lib/simple_mutex.rb +47 -0
- data/simple_mutex.gemspec +40 -0
- metadata +220 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module SimpleMutex
|
|
6
|
+
class Helper
|
|
7
|
+
LIST_MODES = %i[job batch default all].freeze
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def get(lock_key)
|
|
11
|
+
new.get(lock_key)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def list(**options)
|
|
15
|
+
new.list(**options)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get(lock_key)
|
|
20
|
+
raw_data = redis.get(lock_key)
|
|
21
|
+
|
|
22
|
+
return if raw_data.nil?
|
|
23
|
+
|
|
24
|
+
parsed_data = safe_parse(raw_data)
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
key: lock_key,
|
|
28
|
+
value: parsed_data.nil? ? raw_data : parsed_data,
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# rubocop:disable Metrics/MethodLength, Style/HashEachMethods, Performance/CollectionLiteralInLoop
|
|
33
|
+
def list(mode: :default)
|
|
34
|
+
check_mode(mode)
|
|
35
|
+
|
|
36
|
+
result = []
|
|
37
|
+
|
|
38
|
+
redis.keys.each do |lock_key|
|
|
39
|
+
redis.watch(lock_key) do
|
|
40
|
+
raw_data = redis.get(lock_key)
|
|
41
|
+
|
|
42
|
+
unless raw_data.nil?
|
|
43
|
+
parsed_data = safe_parse(raw_data)
|
|
44
|
+
|
|
45
|
+
if parsed_data.nil?
|
|
46
|
+
result << { key: lock_key, value: raw_data } if mode == :all
|
|
47
|
+
else
|
|
48
|
+
lock_type = parsed_data&.dig("payload", "type")
|
|
49
|
+
|
|
50
|
+
if (mode == :all) ||
|
|
51
|
+
(lock_type == "Job" && %i[job default].include?(mode)) ||
|
|
52
|
+
(lock_type == "Batch" && %i[batch default].include?(mode))
|
|
53
|
+
result << { key: lock_key, value: parsed_data }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
redis.unwatch
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
result
|
|
63
|
+
end
|
|
64
|
+
# rubocop:enable Metrics/MethodLength, Style/HashEachMethods, Performance/CollectionLiteralInLoop
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def check_mode(mode)
|
|
69
|
+
return if LIST_MODES.include?(mode)
|
|
70
|
+
raise ::SimpleMutex::Error, "invalid mode ( only [:job, :batch, :default, :all] allowed)."
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def redis
|
|
74
|
+
::SimpleMutex.redis
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def safe_parse(raw_data)
|
|
78
|
+
JSON.parse(raw_data)
|
|
79
|
+
rescue JSON::ParserError
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module SimpleMutex
|
|
7
|
+
class Mutex
|
|
8
|
+
DEFAULT_EXPIRES_IN = 60 * 60 # 1 hour
|
|
9
|
+
|
|
10
|
+
ERR_MSGS = {
|
|
11
|
+
unlock: {
|
|
12
|
+
unknown: lambda do |lock_key|
|
|
13
|
+
"something when wrong when deleting lock key <#{lock_key}>."
|
|
14
|
+
end,
|
|
15
|
+
key_not_found: lambda do |lock_key|
|
|
16
|
+
"lock not found for lock key <#{lock_key}>."
|
|
17
|
+
end,
|
|
18
|
+
signature_mismatch: lambda do |lock_key|
|
|
19
|
+
"signature mismatch for lock key <#{lock_key}>."
|
|
20
|
+
end,
|
|
21
|
+
}.freeze,
|
|
22
|
+
lock: {
|
|
23
|
+
basic: lambda do |lock_key|
|
|
24
|
+
"failed to acquire lock <#{lock_key}>."
|
|
25
|
+
end,
|
|
26
|
+
}.freeze,
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
BaseError = Class.new(::StandardError) do
|
|
30
|
+
attr_reader :lock_key
|
|
31
|
+
|
|
32
|
+
def initialize(msg, lock_key)
|
|
33
|
+
@lock_key = lock_key
|
|
34
|
+
super(msg)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
LockError = Class.new(BaseError)
|
|
39
|
+
UnlockError = Class.new(BaseError)
|
|
40
|
+
|
|
41
|
+
class << self
|
|
42
|
+
attr_accessor :redis
|
|
43
|
+
|
|
44
|
+
def lock(lock_key, **options)
|
|
45
|
+
new(lock_key, **options).lock
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def lock!(lock_key, **options)
|
|
49
|
+
new(lock_key, **options).lock!
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def unlock(lock_key, signature: nil, force: false)
|
|
53
|
+
::SimpleMutex.redis_check!
|
|
54
|
+
|
|
55
|
+
redis = ::SimpleMutex.redis
|
|
56
|
+
|
|
57
|
+
redis.watch(lock_key) do
|
|
58
|
+
raw_data = redis.get(lock_key)
|
|
59
|
+
|
|
60
|
+
if raw_data && (force || signature_valid?(raw_data, signature))
|
|
61
|
+
redis.multi { |multi| multi.del(lock_key) }.first.positive?
|
|
62
|
+
else
|
|
63
|
+
redis.unwatch
|
|
64
|
+
false
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def unlock!(lock_key, signature: nil, force: false)
|
|
70
|
+
::SimpleMutex.redis_check!
|
|
71
|
+
|
|
72
|
+
redis = ::SimpleMutex.redis
|
|
73
|
+
|
|
74
|
+
redis.watch(lock_key) do
|
|
75
|
+
raw_data = redis.get(lock_key)
|
|
76
|
+
|
|
77
|
+
begin
|
|
78
|
+
raise_error(UnlockError, :key_not_found, lock_key) unless raw_data
|
|
79
|
+
|
|
80
|
+
unless force || signature_valid?(raw_data, signature)
|
|
81
|
+
raise_error(UnlockError, :signature_mismatch, lock_key)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
success = redis.multi { |multi| multi.del(lock_key) }.first.positive?
|
|
85
|
+
|
|
86
|
+
raise_error(UnlockError, :unknown, lock_key) unless success
|
|
87
|
+
ensure
|
|
88
|
+
redis.unwatch
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def with_lock(lock_key, **options, &block)
|
|
94
|
+
new(lock_key, **options).with_lock(&block)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def raise_error(error_class, msg_template, lock_key)
|
|
98
|
+
template_base = error_class.name.split("::").last.gsub("Error", "").downcase.to_sym
|
|
99
|
+
error_msg = ERR_MSGS[template_base][msg_template].call(lock_key)
|
|
100
|
+
|
|
101
|
+
raise(error_class.new(error_msg, lock_key))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def signature_valid?(raw_data, signature)
|
|
105
|
+
return false if raw_data.nil?
|
|
106
|
+
|
|
107
|
+
JSON.parse(raw_data)["signature"] == signature
|
|
108
|
+
rescue JSON::ParseError, TypeError
|
|
109
|
+
false
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
attr_reader :lock_key, :expires_in, :signature, :payload
|
|
114
|
+
|
|
115
|
+
def initialize(lock_key,
|
|
116
|
+
expires_in: DEFAULT_EXPIRES_IN,
|
|
117
|
+
signature: SecureRandom.uuid,
|
|
118
|
+
payload: nil)
|
|
119
|
+
::SimpleMutex.redis_check!
|
|
120
|
+
|
|
121
|
+
self.lock_key = lock_key
|
|
122
|
+
self.expires_in = expires_in.to_i
|
|
123
|
+
self.signature = signature
|
|
124
|
+
self.payload = payload
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def lock
|
|
128
|
+
!!redis.set(lock_key, generate_data, nx: true, ex: expires_in)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def unlock(force: false)
|
|
132
|
+
self.class.unlock(lock_key, signature: signature, force: force)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def with_lock
|
|
136
|
+
lock!
|
|
137
|
+
|
|
138
|
+
begin
|
|
139
|
+
yield
|
|
140
|
+
ensure
|
|
141
|
+
unlock
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def lock_obtained?
|
|
146
|
+
self.class.signature_valid?(redis.get(lock_key), signature)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def lock!
|
|
150
|
+
lock or raise_error(LockError, :basic)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def unlock!(force: false)
|
|
154
|
+
self.class.unlock!(lock_key, signature: signature, force: force)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
attr_writer :lock_key, :expires_in, :signature, :payload
|
|
160
|
+
|
|
161
|
+
def generate_data
|
|
162
|
+
JSON.generate(
|
|
163
|
+
"signature" => signature,
|
|
164
|
+
"created_at" => Time.now.to_s,
|
|
165
|
+
"payload" => payload,
|
|
166
|
+
)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def redis
|
|
170
|
+
::SimpleMutex.redis
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def raise_error(error_class, msg_template)
|
|
174
|
+
self.class.raise_error(error_class, msg_template, lock_key)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
|
|
5
|
+
module SimpleMutex
|
|
6
|
+
module SidekiqSupport
|
|
7
|
+
class Batch
|
|
8
|
+
extend Forwardable
|
|
9
|
+
|
|
10
|
+
DEFAULT_EXPIRES_IN = 6 * 60 * 60
|
|
11
|
+
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
|
|
14
|
+
attr_reader :batch, :lock_key, :expires_in
|
|
15
|
+
|
|
16
|
+
def_delegators :@batch, :on, :bid, :description, :description=
|
|
17
|
+
|
|
18
|
+
def initialize(lock_key:, expires_in: DEFAULT_EXPIRES_IN)
|
|
19
|
+
::SimpleMutex.sidekiq_pro_check!
|
|
20
|
+
|
|
21
|
+
self.lock_key = lock_key
|
|
22
|
+
self.expires_in = expires_in
|
|
23
|
+
self.batch = ::Sidekiq::Batch.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def jobs(&block)
|
|
27
|
+
mutex.lock!
|
|
28
|
+
|
|
29
|
+
set_callbacks(mutex.signature)
|
|
30
|
+
|
|
31
|
+
begin
|
|
32
|
+
batch.jobs(&block)
|
|
33
|
+
rescue => error
|
|
34
|
+
mutex.unlock!
|
|
35
|
+
raise error
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
status = ::Sidekiq::Batch::Status.new(batch.bid)
|
|
39
|
+
|
|
40
|
+
if status.total.zero?
|
|
41
|
+
mutex.unlock!
|
|
42
|
+
raise Error, "Batch should contain at least one job."
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
attr_writer :batch, :lock_key, :expires_in
|
|
49
|
+
|
|
50
|
+
def mutex
|
|
51
|
+
return @mutex if defined? @mutex
|
|
52
|
+
|
|
53
|
+
@mutex = ::SimpleMutex::Mutex.new(
|
|
54
|
+
lock_key,
|
|
55
|
+
expires_in: expires_in,
|
|
56
|
+
payload: generate_payload(batch),
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def generate_payload(batch)
|
|
61
|
+
{ "type" => "Batch",
|
|
62
|
+
"started_at" => Time.now.to_s,
|
|
63
|
+
"bid" => batch.bid }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def set_callbacks(signature)
|
|
67
|
+
%i[death success].each do |event|
|
|
68
|
+
batch.on(
|
|
69
|
+
event,
|
|
70
|
+
::SimpleMutex::SidekiqSupport::BatchCallbacks,
|
|
71
|
+
"lock_key" => lock_key,
|
|
72
|
+
"signature" => signature,
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMutex
|
|
4
|
+
module SidekiqSupport
|
|
5
|
+
class BatchCallbacks
|
|
6
|
+
def on_death(_status, options)
|
|
7
|
+
::SimpleMutex::Mutex.unlock!(options["lock_key"], signature: options["signature"])
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def on_success(_status, options)
|
|
11
|
+
::SimpleMutex::Mutex.unlock!(options["lock_key"], signature: options["signature"])
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMutex
|
|
4
|
+
module SidekiqSupport
|
|
5
|
+
class BatchCleaner < ::SimpleMutex::BaseCleaner
|
|
6
|
+
class << self
|
|
7
|
+
def unlock_dead_batches
|
|
8
|
+
new.unlock
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
::SimpleMutex.sidekiq_pro_check!
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def type
|
|
20
|
+
"Batch"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def path_to_entity_id
|
|
24
|
+
%w[payload bid]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def active_entity_ids
|
|
28
|
+
::Sidekiq::BatchSet.new.map(&:bid)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMutex
|
|
4
|
+
module SidekiqSupport
|
|
5
|
+
class JobCleaner < ::SimpleMutex::BaseCleaner
|
|
6
|
+
class << self
|
|
7
|
+
def unlock_dead_jobs
|
|
8
|
+
new.unlock
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def type
|
|
15
|
+
"Job"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def path_to_entity_id
|
|
19
|
+
%w[payload jid]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def active_entity_ids
|
|
23
|
+
::Sidekiq::Workers.new.map { |_pid, _tid, work| work["payload"]["jid"] }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMutex
|
|
4
|
+
module SidekiqSupport
|
|
5
|
+
module JobMixin
|
|
6
|
+
def self.included(klass)
|
|
7
|
+
klass.extend(ClassMethods)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def locking!
|
|
12
|
+
@locking = true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def locking?
|
|
16
|
+
!!@locking
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def skip_locking_error!
|
|
20
|
+
@skip_locking_error = true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def skip_locking_error?
|
|
24
|
+
!!@skip_locking_error
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def lock_with_params!
|
|
28
|
+
@lock_with_params = true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def lock_with_params?
|
|
32
|
+
!!@lock_with_params
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def set_job_timeout(value)
|
|
36
|
+
@job_timeout = value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def job_timeout
|
|
40
|
+
@job_timeout
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def with_redlock(args = [], &block)
|
|
45
|
+
return yield unless self.class.locking?
|
|
46
|
+
|
|
47
|
+
options = {
|
|
48
|
+
params: args,
|
|
49
|
+
lock_with_params: self.class.lock_with_params?,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
options[:expires_in] = self.class.job_timeout unless self.class.job_timeout.nil?
|
|
53
|
+
|
|
54
|
+
::SimpleMutex::SidekiqSupport::JobWrapper.new(self, **options).with_redlock(&block)
|
|
55
|
+
rescue SimpleMutex::Mutex::LockError => error
|
|
56
|
+
process_locking_error(error)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# override for custom processing
|
|
60
|
+
def process_locking_error(error)
|
|
61
|
+
raise error unless self.class.skip_locking_error?
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMutex
|
|
4
|
+
module SidekiqSupport
|
|
5
|
+
class JobWrapper
|
|
6
|
+
attr_reader :job, :params, :lock_key, :lock_with_params, :expires_in
|
|
7
|
+
|
|
8
|
+
DEFAULT_EXPIRES_IN = 5 * 60 * 60 # 5 hours
|
|
9
|
+
|
|
10
|
+
def initialize(job,
|
|
11
|
+
params: [],
|
|
12
|
+
lock_key: nil,
|
|
13
|
+
lock_with_params: false,
|
|
14
|
+
expires_in: DEFAULT_EXPIRES_IN)
|
|
15
|
+
self.job = job
|
|
16
|
+
self.params = params
|
|
17
|
+
|
|
18
|
+
self.lock_key = lock_key
|
|
19
|
+
self.lock_with_params = lock_with_params
|
|
20
|
+
self.expires_in = expires_in
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def with_redlock(&block)
|
|
24
|
+
::SimpleMutex::Mutex.with_lock(
|
|
25
|
+
lock_key || generate_lock_key,
|
|
26
|
+
expires_in: expires_in,
|
|
27
|
+
payload: generate_payload,
|
|
28
|
+
&block
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
attr_writer :job, :params, :lock_key, :lock_with_params, :expires_in
|
|
35
|
+
|
|
36
|
+
def generate_lock_key
|
|
37
|
+
key = if lock_with_params
|
|
38
|
+
"#{job.class.name}<#{params.to_json}>"
|
|
39
|
+
else
|
|
40
|
+
job.class.name
|
|
41
|
+
end
|
|
42
|
+
key.tr(":", "_")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def generate_payload
|
|
46
|
+
{ "type" => "Job",
|
|
47
|
+
"started_at" => Time.now.to_s,
|
|
48
|
+
"jid" => job.jid }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/simple_mutex.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleMutex
|
|
4
|
+
require_relative "simple_mutex/version"
|
|
5
|
+
require_relative "simple_mutex/mutex"
|
|
6
|
+
|
|
7
|
+
require_relative "simple_mutex/base_cleaner"
|
|
8
|
+
|
|
9
|
+
require_relative "simple_mutex/sidekiq_support/job_wrapper"
|
|
10
|
+
require_relative "simple_mutex/sidekiq_support/job_cleaner"
|
|
11
|
+
require_relative "simple_mutex/sidekiq_support/job_mixin"
|
|
12
|
+
|
|
13
|
+
require_relative "simple_mutex/sidekiq_support/batch"
|
|
14
|
+
require_relative "simple_mutex/sidekiq_support/batch_callbacks"
|
|
15
|
+
require_relative "simple_mutex/sidekiq_support/batch_cleaner"
|
|
16
|
+
|
|
17
|
+
require_relative "simple_mutex/helper"
|
|
18
|
+
|
|
19
|
+
class Error < StandardError; end
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
attr_accessor :redis, :logger
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def redis_check!
|
|
26
|
+
raise Error, no_redis_error unless redis
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def sidekiq_pro_check!
|
|
30
|
+
raise Error, no_sidekiq_pro_error unless sidekiq_pro_installed?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def sidekiq_pro_installed?
|
|
34
|
+
Object.const_defined?("Sidekiq::Pro::VERSION")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def no_redis_error
|
|
38
|
+
"You should set SimpleMutex.redis before using any functions of this gem."
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def no_sidekiq_pro_error
|
|
42
|
+
"Batch related functionality requires Sidekiq Pro to be installed."
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module_function :redis_check!, :sidekiq_pro_check!, :sidekiq_pro_installed?,
|
|
46
|
+
:no_redis_error, :no_sidekiq_pro_error
|
|
47
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/simple_mutex/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "simple_mutex"
|
|
7
|
+
spec.version = SimpleMutex::VERSION
|
|
8
|
+
spec.authors = ["bob-umbr"]
|
|
9
|
+
spec.email = ["bob@umbrellio.biz"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Redis-based mutex library for using with sidekiq jobs and batches."
|
|
12
|
+
spec.description = "Redis-based mutex library for using with sidekiq jobs and batches."
|
|
13
|
+
spec.homepage = "https://github.com/umbrellio/simple_mutex"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
|
|
17
|
+
|
|
18
|
+
# Specify which files should be added to the gem when it is released.
|
|
19
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
20
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
spec.bindir = "exe"
|
|
25
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
26
|
+
spec.require_paths = ["lib"]
|
|
27
|
+
|
|
28
|
+
spec.add_runtime_dependency "redis"
|
|
29
|
+
spec.add_runtime_dependency "redis-namespace"
|
|
30
|
+
spec.add_runtime_dependency "sidekiq"
|
|
31
|
+
|
|
32
|
+
spec.add_development_dependency "bundler"
|
|
33
|
+
spec.add_development_dependency "bundler-audit"
|
|
34
|
+
spec.add_development_dependency "mock_redis"
|
|
35
|
+
spec.add_development_dependency "rspec"
|
|
36
|
+
spec.add_development_dependency "rubocop"
|
|
37
|
+
spec.add_development_dependency "rubocop-config-umbrellio"
|
|
38
|
+
spec.add_development_dependency "rubocop-rspec"
|
|
39
|
+
spec.add_development_dependency "timecop"
|
|
40
|
+
end
|