simple_mutex 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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