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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 386f8c08c9a13c4402812145555a49a710fe28bf016455491bb779aa58cd7a90
4
+ data.tar.gz: 1a7e87acc3d54d3a90fd31a515c92c861823e10c054511c1466303fd5afc5e1f
5
+ SHA512:
6
+ metadata.gz: c94e26f6ac88dd2d3be0b7a788051ef0bf8cda52acd41173d18dabb53521c9bb1feb639ba76deac1feccae666a0e3c359563569a5c5fd81175980d87bd324eac
7
+ data.tar.gz: bc0f67a008af4a252e118673584c691de022ad5568b83a088654e9070ad45a20c3fec5c77b2b2abd1f1208ca4831b36a4e276a20fbe49d3854886eda48dfd81f
@@ -0,0 +1,33 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+
9
+ # We want to run on external PRs, but not on our own internal PRs as they'll be run on push event
10
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'umbrellio/table_sync'
11
+
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ ruby: ["2.6", "2.7", "3.0"]
16
+
17
+ name: ${{ matrix.ruby }}
18
+
19
+ steps:
20
+ - uses: actions/checkout@v2
21
+
22
+ - uses: ruby/setup-ruby@v1
23
+ with:
24
+ ruby-version: ${{ matrix.ruby }}
25
+ bundler-cache: true
26
+
27
+ - run: bundle exec rake bundle:audit
28
+ - run: bundle exec rubocop
29
+ - run: bundle exec rspec
30
+
31
+ - uses: coverallsapp/github-action@v1.1.2
32
+ with:
33
+ github-token: ${{ secrets.GITHUB_TOKEN }}
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,14 @@
1
+ inherit_gem:
2
+ rubocop-config-umbrellio: lib/rubocop.yml
3
+
4
+ Layout/HashAlignment:
5
+ EnforcedHashRocketStyle:
6
+ - key
7
+ - table
8
+ EnforcedColonStyle:
9
+ - key
10
+ - table
11
+
12
+ Naming/VariableNumber:
13
+ Exclude:
14
+ - 'spec/**/*'
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,113 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ simple_mutex (1.0.0)
5
+ redis
6
+ redis-namespace
7
+ sidekiq
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activesupport (6.1.4.1)
13
+ concurrent-ruby (~> 1.0, >= 1.0.2)
14
+ i18n (>= 1.6, < 2)
15
+ minitest (>= 5.1)
16
+ tzinfo (~> 2.0)
17
+ zeitwerk (~> 2.3)
18
+ ast (2.4.2)
19
+ bundler-audit (0.9.0.1)
20
+ bundler (>= 1.2.0, < 3)
21
+ thor (~> 1.0)
22
+ concurrent-ruby (1.1.9)
23
+ connection_pool (2.2.5)
24
+ diff-lcs (1.4.4)
25
+ i18n (1.8.10)
26
+ concurrent-ruby (~> 1.0)
27
+ minitest (5.14.4)
28
+ mock_redis (0.29.0)
29
+ ruby2_keywords
30
+ parallel (1.21.0)
31
+ parser (3.0.2.0)
32
+ ast (~> 2.4.1)
33
+ rack (2.2.3)
34
+ rainbow (3.0.0)
35
+ redis (4.5.1)
36
+ redis-namespace (1.8.1)
37
+ redis (>= 3.0.4)
38
+ regexp_parser (2.1.1)
39
+ rexml (3.2.5)
40
+ rspec (3.10.0)
41
+ rspec-core (~> 3.10.0)
42
+ rspec-expectations (~> 3.10.0)
43
+ rspec-mocks (~> 3.10.0)
44
+ rspec-core (3.10.1)
45
+ rspec-support (~> 3.10.0)
46
+ rspec-expectations (3.10.1)
47
+ diff-lcs (>= 1.2.0, < 2.0)
48
+ rspec-support (~> 3.10.0)
49
+ rspec-mocks (3.10.2)
50
+ diff-lcs (>= 1.2.0, < 2.0)
51
+ rspec-support (~> 3.10.0)
52
+ rspec-support (3.10.2)
53
+ rubocop (1.17.0)
54
+ parallel (~> 1.10)
55
+ parser (>= 3.0.0.0)
56
+ rainbow (>= 2.2.2, < 4.0)
57
+ regexp_parser (>= 1.8, < 3.0)
58
+ rexml
59
+ rubocop-ast (>= 1.7.0, < 2.0)
60
+ ruby-progressbar (~> 1.7)
61
+ unicode-display_width (>= 1.4.0, < 3.0)
62
+ rubocop-ast (1.12.0)
63
+ parser (>= 3.0.1.1)
64
+ rubocop-config-umbrellio (1.17.0.53)
65
+ rubocop (= 1.17.0)
66
+ rubocop-performance (= 1.10.0)
67
+ rubocop-rails (= 2.9.1)
68
+ rubocop-rake (= 0.5.1)
69
+ rubocop-rspec (= 2.2.0)
70
+ rubocop-sequel (= 0.2.0)
71
+ rubocop-performance (1.10.0)
72
+ rubocop (>= 0.90.0, < 2.0)
73
+ rubocop-ast (>= 0.4.0)
74
+ rubocop-rails (2.9.1)
75
+ activesupport (>= 4.2.0)
76
+ rack (>= 1.1)
77
+ rubocop (>= 0.90.0, < 2.0)
78
+ rubocop-rake (0.5.1)
79
+ rubocop
80
+ rubocop-rspec (2.2.0)
81
+ rubocop (~> 1.0)
82
+ rubocop-ast (>= 1.1.0)
83
+ rubocop-sequel (0.2.0)
84
+ rubocop (~> 1.0)
85
+ ruby-progressbar (1.11.0)
86
+ ruby2_keywords (0.0.5)
87
+ sidekiq (6.2.2)
88
+ connection_pool (>= 2.2.2)
89
+ rack (~> 2.0)
90
+ redis (>= 4.2.0)
91
+ thor (1.1.0)
92
+ timecop (0.9.4)
93
+ tzinfo (2.0.4)
94
+ concurrent-ruby (~> 1.0)
95
+ unicode-display_width (2.1.0)
96
+ zeitwerk (2.5.1)
97
+
98
+ PLATFORMS
99
+ ruby
100
+
101
+ DEPENDENCIES
102
+ bundler
103
+ bundler-audit
104
+ mock_redis
105
+ rspec
106
+ rubocop
107
+ rubocop-config-umbrellio
108
+ rubocop-rspec
109
+ simple_mutex!
110
+ timecop
111
+
112
+ BUNDLED WITH
113
+ 2.2.22
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Umbrellio
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,364 @@
1
+ # SimpleMutex
2
+
3
+ `SimpleMutex::Mutex` - Redis-based locks with ability to store custom data inside them.
4
+
5
+ `SimpleMutex::SidekiqSupport::JobWrapper` - wrapper for Sidekiq jobs that generates locks using
6
+ job's class name and arguments (optional)
7
+
8
+ `SimpleMutex:SidekiqSupport::JobMixin` - mixin for Sidekiq jobs with DSL simplifying usage
9
+ of `SimpleMutex::SidekiqSupport::JobWrapper`
10
+
11
+ `SimpleMutex::SidekiqSupport::JobCleaner` - cleaner for leftover locks created by SimpleMutex::Job
12
+ if Sidekiq dies unexpectedly.
13
+
14
+ `SimpleMutex::SidekiqSupport::Batch` - wrapper for Sidekiq Pro batches that use SimpleMutex::Mutex
15
+ to prevent running multiple batch instances.
16
+
17
+ `SimpleMutex:SidekiqSupport::BatchCleaner` - cleaner for leftover lock created by SimpleMutex::Batch
18
+ if Sidekiq dies unexpectedly.
19
+
20
+ `SimpleMutex::Helper` - auxiliary class for debugging purposes. Allows to inspect existing locks.
21
+
22
+
23
+ ## Configuration
24
+
25
+ Providing Redis instance before using gem is mandatory.
26
+
27
+ ```ruby
28
+ SimpleMutex.redis = Redis.new(
29
+ # ...
30
+ )
31
+ ```
32
+
33
+ Providing logger is optional (used by `SimpleMutex::SidekiqSupport::JobCleaner` and
34
+ `SimpleMutex::SidekiqSupport::BatchCleaner`).
35
+
36
+ ```ruby
37
+ SimpleMutex.logger = Logger.new(
38
+ # ...
39
+ )
40
+ ```
41
+
42
+ When using gem with Ruby on Rails you can set those in initializers
43
+
44
+ ## SimpleMutex::Mutex Usage
45
+
46
+ ### Initialization
47
+
48
+ #### Arguments
49
+
50
+ ##### mandatory
51
+
52
+ * `lock_key` - string that identifies lock, mandatory. Two pieces of locked code can't be run
53
+ simultaneously if they use same `lock_key`. They don't interfere with each other if different
54
+ `lock_key`'s are used
55
+
56
+ ##### optional
57
+
58
+ Keyword arguments are used for optional args.
59
+
60
+ * `expires_in:` - mutex TTL in second (or ActiveSupport::Numeric time interval), lock will be
61
+ removed by redis automatically when expired, lock will expire in 1 hour (`3600`) if not provided
62
+ * `signature:` - string used to determine ownership of lock, checked when manually deleting lock,
63
+ will be generated by `SecureRandom.uuid` if not provided
64
+ * `payload:` - any object that can be serialized as JSON, `nil` if not provided
65
+
66
+ #### Example
67
+
68
+ ```ruby
69
+ SimpleMutex::Mutex
70
+ .new(
71
+ "some_lock_key",
72
+ expires_in: 3600,
73
+ signature: "qwe123",
74
+ payload: { "started_at" => Time.now }
75
+ )
76
+ ```
77
+
78
+ ### Wrapping block in mutex
79
+
80
+ You can use method `#with_lock` to wrap code block in mutex
81
+
82
+ ```ruby
83
+ SimpleMutex::Mutex
84
+ .new(
85
+ "some_lock_key",
86
+ expires_in: 3600,
87
+ signature: "qwe123",
88
+ payload: { "started_at" => Time.now }
89
+ ).with_lock do
90
+ # your code
91
+ end
92
+ ```
93
+
94
+ Method has delegator defined on class, so it can be used without manual instantiation
95
+
96
+ ```ruby
97
+ SimpleMutex::Mutex
98
+ .with_lock(
99
+ "some_lock_key",
100
+ expires_in: 3600,
101
+ signature: "qwe123",
102
+ payload: { "started_at" => Time.now }
103
+ ) do
104
+ # your code
105
+ end
106
+ ```
107
+
108
+ ### Manual lock control
109
+
110
+ ##### Using mutex instance
111
+
112
+ ```ruby
113
+ mutex = SimpleMutex::Mutex.new(
114
+ "some_lock_key",
115
+ expires_in: 3600,
116
+ signature: "qwe123",
117
+ payload: { "started_at" => Time.now }
118
+ )
119
+
120
+ mutex.lock!
121
+ # your code
122
+ mutex.unlock!
123
+ ```
124
+
125
+ If you for some reason don't want exceptions to be raised when obtaining/deleting lock is failed,
126
+ you can use non-! methods.
127
+
128
+ ```ruby
129
+ mutex = SimpleMutex::Mutex.new("some_lock_key")
130
+ # obtaining of lock is not guaranteed
131
+ mutex.lock
132
+ # but you can check if it is obtained (true if lock with correct signature exists)
133
+ mutex.lock_obtained?
134
+ # releasing of lock is not guaranteed
135
+ mutex.unlock
136
+ ```
137
+
138
+ ##### Using without instance
139
+
140
+ There are `::lock`/`::lock!`/`::unlock`/`::unlock!` methods defined on class if you don't want to
141
+ explicitly use initializer (though it still will be used behind the scenes as `::lock` and `::lock!`
142
+ class methods are just delegators).
143
+
144
+ Mutexes have random `signature` stored inside to determine ownership. By default it prevents
145
+ deleting locks with signature different from provided. You can use `force: true` to ignore
146
+ signature check.
147
+
148
+ `::lock` and `::lock!` class methods accept same arguments as in `::new`
149
+
150
+ `::unlock` and `::unlock!` accept next arguments:
151
+
152
+ * `lock_key` - same as in `::new`
153
+ * `signature:` - same as in `::new`
154
+ * `force:` - boolean, signature will be ignored if `true`, optional, `false` by default
155
+
156
+ ```ruby
157
+ SimpleMutex::Mutex.lock!("some_lock_key", signature: "abra_kadabra")
158
+
159
+ # This will work because signature is same as in lock
160
+ SimpleMutex::Mutex.unlock!("some_lock_key", signature: "abra_kadabra")
161
+
162
+ # This won't work, because signature is missing
163
+ SimpleMutex::Mutex.unlock!("some_lock_key")
164
+
165
+ # This won't work, because signature is different
166
+ SimpleMutex::Mutex.unlock!("some_lock_key", signature: "alakazam")
167
+
168
+ # This will work because of force: true
169
+ SimpleMutex::Mutex.unlock!("some_lock_key", force: true)
170
+
171
+ # This will work because of force: true
172
+ SimpleMutex::Mutex.unlock!("some_lock_key", signature: "alakazam", force: true)
173
+ ```
174
+
175
+ ### Getting signature from instance
176
+
177
+ You can get signature from instance if you want. By default it is UUID generated by SecureRandom.
178
+
179
+ ```ruby
180
+ mutex = SimpleMutex::Mutex.new("some_lock_key")
181
+ mutex.signature
182
+ ```
183
+
184
+ ## SimpleMutex::SidekiqSupport::JobWrapper Usage
185
+
186
+ This class made to simplify usage for locking of sidekiq jobs. It will create lock with
187
+ `lock_key` based on job's `class.name` and it's arguments if `lock_with_params: true`.
188
+
189
+ Job's ID (`jid`) and time when job's execution is started will be stored inside mutex value.
190
+
191
+ ```ruby
192
+ class SomeJob
193
+ include Sidekiq::Worker
194
+
195
+ def perform(*args)
196
+ SimpleMutex::SidekiqSupport::JobWrapper.new(
197
+ self,
198
+ params: args,
199
+ lock_with_params: true,
200
+ expires_in: 1.hour,
201
+ payload: { this_is_optional: true }
202
+ ).with_redlock do
203
+ # your code
204
+ end
205
+ end
206
+ end
207
+ ```
208
+
209
+ `params` will be used to generate `lock_key` if `lock_with_params: true`.
210
+
211
+ `expires_in:` is in seconds, optional, 5 hours by default.
212
+
213
+ `payload:` optional serializable object.
214
+
215
+ ## SimpleMutex::SidekiqSupport::Batch Usage
216
+
217
+ This is wrapper for `Sidekiq::Batch` (from Sidekiq Pro) that helps to prevent running two
218
+ similar batches.
219
+
220
+ ```ruby
221
+ batch = SimpleMutex::SidekiqSupport::Batch.new(
222
+ lock_key: "my_batch",
223
+ expires_in: 23.hours.to_i,
224
+ )
225
+
226
+ batch.description = "batch of MyJobs"
227
+ batch.on(:success, self.class, {}) # you can add custom callbacks like with Sidekiq::Batch
228
+ batch.on(:death , self.class, {})
229
+
230
+ batch.jobs do
231
+ set_of_job_attributes.each do |job_attributes|
232
+ MyJob.perform(job_attributes)
233
+ end
234
+ end
235
+ ```
236
+
237
+ * `lock_key` - manatory lock key
238
+ * `expires_in:` - optional TTL, 6 hours if not provided
239
+
240
+ ## SimpleMutex::SidekiqSupport::JobCleaner Usage
241
+
242
+ If you use SimpleMutex for locking jobs via `SimpleMutex::SidekiqSupport::Job`, when Sidekiq dies
243
+ unexpectedely, there can be leftover mutexes for dead jobs. To delete them you can use:
244
+
245
+ ```ruby
246
+ SimpleMutex::SidekiqSupport::JobCleaner.unlock_dead_jobs
247
+ ```
248
+
249
+
250
+ ## SimpleMutex::SidekiqSupport::BatchCleaner Usage
251
+
252
+ If you use SimpleMutex for locking Batches via `SimpleMutex::SidekiqSupport::Batch`, when Sidekiq
253
+ dies unexpectedely, there can be leftover mutexes for dead batches. To delete them you can use:
254
+
255
+ ```ruby
256
+ SimpleMutex::SidekiqSupport::BatchCleaner.unlock_dead_batches
257
+ ```
258
+
259
+ ## SimpleMutex::Helper Usage
260
+
261
+ Getting lock by `lock_key` (returns nil if no such lock)
262
+
263
+ ```ruby
264
+ SimpleMutex::Helper.get("some_lock_key")
265
+ ```
266
+
267
+ Listing existing locks.
268
+
269
+ ```ruby
270
+ SimpleMutex::Helper.list(mode: :default)
271
+ ```
272
+
273
+ `mode:` paramater allows to filter locks by type:
274
+ * `:all` - all locks including manual
275
+ * `:job` - job locks
276
+ * `:batch` - batch locks
277
+ * `:default` - job and batch locks
278
+
279
+ ## SimpleMutex::SidekiqSupport::JobMixin Usage
280
+
281
+ Base Job class
282
+
283
+ ```ruby
284
+ class ApplicationJob
285
+ include Sidekiq::Worker
286
+ include SimpleMutex::SidekiqSupport::JobMixin
287
+
288
+ class << self
289
+ def inherited(job_class)
290
+ # Setting default timeout for mutex.
291
+ job_class.set_job_timeout(5 * 60 * 60) # 5 hours
292
+
293
+ job_class.prepend(
294
+ Module.new do
295
+ def perform(*args)
296
+ with_redlock(args) { super }
297
+ end
298
+ end,
299
+ )
300
+ end
301
+ end
302
+ end
303
+ ```
304
+
305
+ DSL:
306
+ - `locking!` - enables locking with simple_mutex for jobs of this class
307
+ - `lock_with_params!` - locks are specific for set of arguments. Same job with other arguments
308
+ can still be called.
309
+ - `skip_locking_error?` - suppresses `SimpleMutex::Mutex::LockError`
310
+ - `set_job_timeout` - redis mutex TTL in seconds (will be removed by redis itself on timeout)
311
+
312
+ Example:
313
+
314
+ ```ruby
315
+ class SpecificJob < ApplicaionJob
316
+ locking!
317
+ lock_with_params!
318
+ set_job_timeout 6 * 60 * 60
319
+
320
+ def perform
321
+ # ...
322
+ end
323
+ end
324
+ ```
325
+
326
+ You can also override error processing for SimpleMutex::Mutex::LockError
327
+
328
+ ```ruby
329
+ # DEFAULT ERROR PROCESSING
330
+ # def process_locking_error(error)
331
+ # raise error unless self.class.skip_locking_error?
332
+ # end
333
+
334
+ class SpecificJob < ApplicaionJob
335
+ locking!
336
+
337
+ def perform
338
+ # ...
339
+ end
340
+
341
+ def process_locking_error(error)
342
+ SomeLogger.error(error.msg)
343
+ raise error unless self.class.skip_locking_error?
344
+ end
345
+ end
346
+ ```
347
+
348
+ ## Contributing
349
+
350
+ Bug reports and pull requests are welcome on GitHub at https://github.com/umbrellio/simple_mutex.
351
+
352
+ ## License
353
+
354
+ Released under MIT License.
355
+
356
+ ## Authors
357
+
358
+ Team Umbrellio
359
+
360
+ ---
361
+
362
+ <a href="https://github.com/umbrellio/">
363
+ <img style="float: left;" src="https://umbrellio.github.io/Umbrellio/supported_by_umbrellio.svg" alt="Supported by Umbrellio" width="439" height="72">
364
+ </a>
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "simple_mutex"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleMutex
4
+ class BaseCleaner
5
+ def unlock
6
+ ::SimpleMutex.redis_check!
7
+
8
+ logger&.info(start_msg)
9
+
10
+ redis.keys.select do |lock_key|
11
+ redis.watch(lock_key) do
12
+ raw_data = redis.get(lock_key)
13
+
14
+ next redis.unwatch if raw_data.nil?
15
+
16
+ parsed_data = safe_parse(raw_data)
17
+
18
+ next redis.unwatch unless parsed_data&.dig("payload", "type") == type
19
+
20
+ entity_id = parsed_data&.dig(*path_to_entity_id)
21
+
22
+ next redis.unwatch if entity_id.nil? || active?(entity_id)
23
+
24
+ return_value = redis.multi { redis.del(lock_key) }
25
+
26
+ log_iteration(lock_key, raw_data, return_value) unless logger.nil?
27
+
28
+ return_value.first.positive?
29
+ end
30
+ end
31
+
32
+ logger&.info(end_msg)
33
+ end
34
+
35
+ private
36
+
37
+ def active?(entity_id)
38
+ active_entity_ids.include?(entity_id)
39
+ end
40
+
41
+ def type
42
+ raise NoMethodError
43
+ end
44
+
45
+ def path_to_entity_id
46
+ raise NoMethodError
47
+ end
48
+
49
+ def get_active_entity_ids
50
+ raise NoMethodError
51
+ end
52
+
53
+ def safe_parse(raw_data)
54
+ JSON.parse(raw_data)
55
+ rescue JSON::ParserError, TypeError
56
+ nil
57
+ end
58
+
59
+ def active_entity_ids
60
+ return @active_entity_ids if defined? @active_entity_ids
61
+
62
+ @active_entity_ids = get_active_entity_ids
63
+ end
64
+
65
+ def log_iteration(lock_key, raw_data, return_value)
66
+ log_msg = generate_log_msg(lock_key, raw_data, return_value)
67
+
68
+ if return_value&.first.is_a?(Integer)
69
+ logger.info(log_msg)
70
+ else
71
+ logger.error(log_msg)
72
+ end
73
+ end
74
+
75
+ def start_msg
76
+ "START #{self.class.name}"
77
+ end
78
+
79
+ def end_msg
80
+ "END #{self.class.name}"
81
+ end
82
+
83
+ def generate_log_msg(lock_key, raw_data, return_value)
84
+ "Trying to delete row with key <#{lock_key.inspect}> "\
85
+ "and value <#{raw_data.inspect}>. "\
86
+ "MULTI returned value <#{return_value.inspect}>."
87
+ end
88
+
89
+ def redis
90
+ ::SimpleMutex.redis
91
+ end
92
+
93
+ def logger
94
+ ::SimpleMutex.logger
95
+ end
96
+ end
97
+ end