redis_queued_locks 0.0.21 → 0.0.23

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ea008491f8c631aea2677c6571a206db919c0ca5d599b58adc37fa82c372145
4
- data.tar.gz: 41e5bece44a3b1af1031d28a4d7bfe480641151573b97c89d62841f296cbc3ca
3
+ metadata.gz: 05a8fc14cae41bf9870bedf4eae9fa298c3704b57d25df428c2e2c8bd9bf0223
4
+ data.tar.gz: 14a941048edc7679df1d80805a96d3db4019d45acfc80ac5444d99e12cc3eadc
5
5
  SHA512:
6
- metadata.gz: cf96a4535a3fa8f6c7a62797805263e7957f198997213df65c164b9261d5199526f7dbc6e2a3f5e13a912e79cc7cd718ed193b0f197586cd03f1f3f95d958fca
7
- data.tar.gz: 46726b0d18cd73d108b57b34872f1369ab63771a7d6c899aee021ab628ced537e6e0a7e255aecee259b902d682f5d13a12519dcc7033b786ef832c035a61f12a
6
+ metadata.gz: 292f4ed74242a1a53b96e290e7759db0b1c918070f0875805bedbe78360fac71b6ac81ec09b63836002b2a17ab247ca06f9cf7ff828a4a07f924b2588e24fd3f
7
+ data.tar.gz: 270749b6a32418e2a57b3995b5ef3b826b6066a97a56efd08f9305f0ed314ed7da13f9f395f891ab619ceea3d4bde53c880278758fbf42fb1e84b98fa5b14690
data/CHANGELOG.md CHANGED
@@ -1,8 +1,17 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.23] - 2024-03-21
4
+ ### Changed
5
+ - Composed redis commands are invoked on the one conenction
6
+ (instead of mutiple connection fetchiong from redis connection pool on each redis command);
7
+
8
+ ## [0.0.22] - 2024-03-21
9
+ ### Added
10
+ - Logging infrastructure. Initial implementation includes the only debugging features.
11
+
3
12
  ## [0.0.21] - 2024-03-19
4
13
  ### Changed
5
- - `RedisQueuedLocks::Acquier` refactirngs;
14
+ - Refactored `RedisQueuedLocks::Acquier`;
6
15
 
7
16
  ## [0.0.20] - 2024-03-14
8
17
  ### Added
data/README.md CHANGED
@@ -134,6 +134,16 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
134
134
  # - it is calculated once per `RedisQueudLocks::Client` instance;
135
135
  # - expects the proc object;
136
136
  config.uniq_identifier = -> { RedisQueuedLocks::Resource.calc_uniq_identity }
137
+
138
+ # (default: RedisQueuedLocks::Logging::VoidLogger)
139
+ # - the logger object;
140
+ # - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
141
+ # - at this moment the only debug logs are realised in 3 cases:
142
+ # - start of lock obtaining: "[redis_queud_locks.start_lock_obtaining] lock_key => 'rql:lock:your_lock'"
143
+ # - finish of the lock obtaining: "[redis_queued_locks.lock_obtained] lock_key => 'rql:lock:your_lock' acq_time => 123.456 (ms)"
144
+ # - start of the lock expiration after `yield`: "[redis_queud_locks.expire_lock] lock_key => 'rql:lock:your_lock'"
145
+ # - by default uses VoidLogger that does nothing;
146
+ config.logger = RedisQueuedLocks::Logging::VoidLogger
137
147
  end
138
148
  ```
139
149
 
@@ -581,6 +591,7 @@ Detalized event semantics and payload structure:
581
591
  - an ability to add custom metadata to the lock and an ability to read this data;
582
592
  - lock prioritization;
583
593
  - support for LIFO strategy;
594
+ - structured logging;
584
595
  - **Minor**
585
596
  - GitHub Actions CI;
586
597
  - `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
@@ -31,95 +31,101 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
31
31
  timestamp = nil
32
32
 
33
33
  # Step 0: watch the lock key changes (and discard acquirement if lock is already acquired)
34
- result = redis.multi(watch: [lock_key]) do |transact|
35
- # Fast-Step X0: fail-fast check
36
- if fail_fast && redis.call('HGET', lock_key, 'acq_id')
37
- # Fast-Step X1: is lock already obtained. fail fast - no try.
38
- inter_result = :fail_fast_no_try
39
- else
40
- # Step 1: add an acquier to the lock acquirement queue
41
- res = redis.call('ZADD', lock_key_queue, 'NX', acquier_position, acquier_id)
42
-
43
- RedisQueuedLocks.debug(
44
- "Step №1: добавление в очередь (#{acquier_id}). [ZADD to the queue: #{res}]"
45
- )
46
-
47
- # Step 2.1: drop expired acquiers from the lock queue
48
- res = redis.call(
49
- 'ZREMRANGEBYSCORE',
50
- lock_key_queue,
51
- '-inf',
52
- RedisQueuedLocks::Resource.acquier_dead_score(queue_ttl)
53
- )
54
-
55
- RedisQueuedLocks.debug(
56
- "Step №2: дропаем из очереди просроченных ожидающих. [ZREMRANGE: #{res}]"
57
- )
58
-
59
- # Step 3: get the actual acquier waiting in the queue
60
- waiting_acquier = Array(redis.call('ZRANGE', lock_key_queue, '0', '0')).first
61
-
62
- RedisQueuedLocks.debug(
63
- "Step №3: какой процесс в очереди сейчас ждет. " \
64
- "[ZRANGE <следующий процесс>: #{waiting_acquier} :: <текущий процесс>: #{acquier_id}]"
65
- )
66
-
67
- # Step 4: check the actual acquier: is it ours? are we aready to lock?
68
- unless waiting_acquier == acquier_id
69
- # Step ROLLBACK 1.1: our time hasn't come yet. retry!
34
+ result = redis.with do |rconn|
35
+ rconn.multi(watch: [lock_key]) do |transact|
36
+ # Fast-Step X0: fail-fast check
37
+ if fail_fast && rconn.call('HGET', lock_key, 'acq_id')
38
+ # Fast-Step X1: is lock already obtained. fail fast - no try.
39
+ inter_result = :fail_fast_no_try
40
+ else
41
+ # Step 1: add an acquier to the lock acquirement queue
42
+ res = rconn.call('ZADD', lock_key_queue, 'NX', acquier_position, acquier_id)
70
43
 
71
44
  RedisQueuedLocks.debug(
72
- "Step ROLLBACK №1: не одинаковые ключи. выходим. " \
73
- "[Ждет: #{waiting_acquier}. А нужен: #{acquier_id}]"
45
+ "Step №1: добавление в очередь (#{acquier_id}). [ZADD to the queue: #{res}]"
74
46
  )
75
47
 
76
- inter_result = :acquier_is_not_first_in_queue
77
- else
78
- # NOTE: our time has come! let's try to acquire the lock!
48
+ # Step 2.1: drop expired acquiers from the lock queue
49
+ res = rconn.call(
50
+ 'ZREMRANGEBYSCORE',
51
+ lock_key_queue,
52
+ '-inf',
53
+ RedisQueuedLocks::Resource.acquier_dead_score(queue_ttl)
54
+ )
55
+
56
+ RedisQueuedLocks.debug(
57
+ "Step №2: дропаем из очереди просроченных ожидающих. [ZREMRANGE: #{res}]"
58
+ )
79
59
 
80
- # Step 5: check if the our lock is already acquired
81
- locked_by_acquier = redis.call('HGET', lock_key, 'acq_id')
60
+ # Step 3: get the actual acquier waiting in the queue
61
+ waiting_acquier = Array(rconn.call('ZRANGE', lock_key_queue, '0', '0')).first
82
62
 
83
63
  RedisQueuedLocks.debug(
84
- "Ste5: Ищем требуемый лок. " \
85
- "[HGET<#{lock_key}>: " \
86
- "#{(locked_by_acquier == nil) ? 'не занят' : "занят процессом <#{locked_by_acquier}>"}"
64
+ "Step3: какой процесс в очереди сейчас ждет. " \
65
+ "[ZRANGE <следующий процесс>: #{waiting_acquier} :: <текущий процесс>: #{acquier_id}]"
87
66
  )
88
67
 
89
- if locked_by_acquier
90
- # Step ROLLBACK 2: required lock is stil acquired. retry!
68
+ # Step 4: check the actual acquier: is it ours? are we aready to lock?
69
+ unless waiting_acquier == acquier_id
70
+ # Step ROLLBACK 1.1: our time hasn't come yet. retry!
91
71
 
92
72
  RedisQueuedLocks.debug(
93
- "Step ROLLBACK №2: Ключ уже занят. Ничего не делаем. " \
94
- "[Занят процессом: #{locked_by_acquier}]"
73
+ "Step ROLLBACK №1: не одинаковые ключи. выходим. " \
74
+ "[Ждет: #{waiting_acquier}. А нужен: #{acquier_id}]"
95
75
  )
96
76
 
97
- inter_result = :lock_is_still_acquired
77
+ inter_result = :acquier_is_not_first_in_queue
98
78
  else
99
- # NOTE: required lock is free and ready to be acquired! acquire!
79
+ # NOTE: our time has come! let's try to acquire the lock!
100
80
 
101
- # Step 6.1: remove our acquier from waiting queue
102
- transact.call('ZPOPMIN', lock_key_queue, '1')
81
+ # Step 5: check if the our lock is already acquired
82
+ locked_by_acquier = rconn.call('HGET', lock_key, 'acq_id')
103
83
 
84
+ # rubocop:disable Layout/LineLength
104
85
  RedisQueuedLocks.debug(
105
- 'Step4: Забираем наш текущий процесс из очереди. [ZPOPMIN]'
86
+ "Ste5: Ищем требуемый лок. " \
87
+ "[HGET<#{lock_key}>: " \
88
+ "#{(locked_by_acquier == nil) ? 'не занят' : "занят процессом <#{locked_by_acquier}>"}"
106
89
  )
107
-
108
- RedisQueuedLocks.debug(
109
- "===> <FINAL> Step №6: закрепляем лок за процессом [HSET<#{lock_key}>: #{acquier_id}]"
110
- )
111
-
112
- # Step 6.2: acquire a lock and store an info about the acquier
113
- transact.call(
114
- 'HSET',
115
- lock_key,
116
- 'acq_id', acquier_id,
117
- 'ts', (timestamp = Time.now.to_i),
118
- 'ini_ttl', ttl
119
- )
120
-
121
- # Step 6.3: set the lock expiration time in order to prevent "infinite locks"
122
- transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
90
+ # rubocop:enable Layout/LineLength
91
+
92
+ if locked_by_acquier
93
+ # Step ROLLBACK 2: required lock is stil acquired. retry!
94
+
95
+ RedisQueuedLocks.debug(
96
+ "Step ROLLBACK №2: Ключ уже занят. Ничего не делаем. " \
97
+ "[Занят процессом: #{locked_by_acquier}]"
98
+ )
99
+
100
+ inter_result = :lock_is_still_acquired
101
+ else
102
+ # NOTE: required lock is free and ready to be acquired! acquire!
103
+
104
+ # Step 6.1: remove our acquier from waiting queue
105
+ transact.call('ZPOPMIN', lock_key_queue, '1')
106
+
107
+ RedisQueuedLocks.debug(
108
+ 'Step №4: Забираем наш текущий процесс из очереди. [ZPOPMIN]'
109
+ )
110
+
111
+ # rubocop:disable Layout/LineLength
112
+ RedisQueuedLocks.debug(
113
+ "===> <FINAL> Step №6: закрепляем лок за процессом [HSET<#{lock_key}>: #{acquier_id}]"
114
+ )
115
+ # rubocop:enable Layout/LineLength
116
+
117
+ # Step 6.2: acquire a lock and store an info about the acquier
118
+ transact.call(
119
+ 'HSET',
120
+ lock_key,
121
+ 'acq_id', acquier_id,
122
+ 'ts', (timestamp = Time.now.to_i),
123
+ 'ini_ttl', ttl
124
+ )
125
+
126
+ # Step 6.3: set the lock expiration time in order to prevent "infinite locks"
127
+ transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
128
+ end
123
129
  end
124
130
  end
125
131
  end
@@ -3,7 +3,11 @@
3
3
  # @api private
4
4
  # @since 0.1.0
5
5
  module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
6
+ # @since 0.1.0
7
+ extend RedisQueuedLocks::Utilities
8
+
6
9
  # @param redis [RedisClient] Redis connection manager.
10
+ # @param logger [#debug] Logger object.
7
11
  # @param lock_key [String] Lock key to be expired.
8
12
  # @param timed [Boolean] Should the lock be wrapped by Tiemlout with with lock's ttl
9
13
  # @param ttl_shift [Float] Lock's TTL shifting. Should affect block's ttl. In millisecodns.
@@ -13,7 +17,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
13
17
  #
14
18
  # @api private
15
19
  # @since 0.1.0
16
- def yield_with_expire(redis, lock_key, timed, ttl_shift, ttl, &block)
20
+ def yield_with_expire(redis, logger, lock_key, timed, ttl_shift, ttl, &block)
17
21
  if block_given?
18
22
  if timed && ttl != nil
19
23
  timeout = ((ttl - ttl_shift) / 1000.0).yield_self { |time| (time < 0) ? 0.0 : time }
@@ -23,6 +27,9 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
23
27
  end
24
28
  end
25
29
  ensure
30
+ run_non_critical do
31
+ logger.debug("[redis_queued_locks.expire_lock] lock_key => '#{lock_key}'")
32
+ end
26
33
  redis.call('EXPIRE', lock_key, '0')
27
34
  end
28
35
 
@@ -69,6 +69,9 @@ module RedisQueuedLocks::Acquier::AcquireLock
69
69
  # already obtained.
70
70
  # @option metadata [NilClass,Any]
71
71
  # - A custom metadata wich will be passed to the instrumenter's payload with :meta key;
72
+ # @option logger [#debug]
73
+ # - Logger object used from `configuration` layer (see config[:logger]);
74
+ # - See RedisQueuedLocks::Logging::VoidLogger for example;
72
75
  # @param [Block]
73
76
  # A block of code that should be executed after the successfully acquired lock.
74
77
  # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>,yield]
@@ -96,6 +99,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
96
99
  identity:,
97
100
  fail_fast:,
98
101
  metadata:,
102
+ logger:,
99
103
  &block
100
104
  )
101
105
  # Step 1: prepare lock requirements (generate lock name, calc lock ttl, etc).
@@ -127,6 +131,13 @@ module RedisQueuedLocks::Acquier::AcquireLock
127
131
  }
128
132
  acq_dequeue = -> { dequeue_from_lock_queue(redis, lock_key_queue, acquier_id) }
129
133
 
134
+ run_non_critical do
135
+ logger.debug(
136
+ "[redis_queued_locks.start_lock_obtaining] " \
137
+ "lock_key => '#{lock_key}'"
138
+ )
139
+ end
140
+
130
141
  # Step 2: try to lock with timeout
131
142
  with_acq_timeout(timeout, lock_key, raise_errors, on_timeout: acq_dequeue) do
132
143
  acq_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
@@ -153,15 +164,25 @@ module RedisQueuedLocks::Acquier::AcquireLock
153
164
 
154
165
  # Step 2.1: analyze an acquirement attempt
155
166
  if ok
167
+ run_non_critical do
168
+ logger.debug(
169
+ "[redis_queued_locks.lock_obtained] " \
170
+ "lock_key => '#{result[:lock_key]}'" \
171
+ "acq_time => #{acq_time} (ms)"
172
+ )
173
+ end
174
+
156
175
  # Step X (instrumentation): lock obtained
157
- instrumenter.notify('redis_queued_locks.lock_obtained', {
158
- lock_key: result[:lock_key],
159
- ttl: result[:ttl],
160
- acq_id: result[:acq_id],
161
- ts: result[:ts],
162
- acq_time: acq_time,
163
- meta: metadata
164
- })
176
+ run_non_critical do
177
+ instrumenter.notify('redis_queued_locks.lock_obtained', {
178
+ lock_key: result[:lock_key],
179
+ ttl: result[:ttl],
180
+ acq_id: result[:acq_id],
181
+ ts: result[:ts],
182
+ acq_time: acq_time,
183
+ meta: metadata
184
+ })
185
+ end
165
186
 
166
187
  # Step 2.1.a: successfully acquired => build the result
167
188
  acq_process[:lock_info] = {
@@ -225,7 +246,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
225
246
  begin
226
247
  yield_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
227
248
  ttl_shift = ((yield_time - acq_process[:acq_end_time]) * 1000).ceil(2)
228
- yield_with_expire(redis, lock_key, timed, ttl_shift, ttl, &block)
249
+ yield_with_expire(redis, logger, lock_key, timed, ttl_shift, ttl, &block)
229
250
  ensure
230
251
  acq_process[:rel_time] = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
231
252
  acq_process[:hold_time] = (
@@ -7,11 +7,12 @@ module RedisQueuedLocks::Acquier::ExtendLockTTL
7
7
  # @param redis_client [RedisClient]
8
8
  # @param lock_name [String]
9
9
  # @param milliseconds [Integer]
10
+ # @param logger [#debug]
10
11
  # @return [?]
11
12
  #
12
13
  # @api private
13
14
  # @since 0.1.0
14
- def extend_lock_ttl(redis_client, lock_name, milliseconds)
15
+ def extend_lock_ttl(redis_client, lock_name, milliseconds, logger)
15
16
  # TODO: realize
16
17
  end
17
18
  end
@@ -17,12 +17,15 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
17
17
  # The number of lock keys that should be released in a time.
18
18
  # @param isntrumenter [#notify]
19
19
  # See RedisQueuedLocks::Instrument::ActiveSupport for example.
20
+ # @param logger [#debug]
21
+ # - Logger object used from `configuration` layer (see config[:logger]);
22
+ # - See RedisQueuedLocks::Logging::VoidLogger for example;
20
23
  # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
21
24
  # Format: { ok: true/false, result: Hash<Symbol,Numeric> }
22
25
  #
23
26
  # @api private
24
27
  # @since 0.1.0
25
- def release_all_locks(redis, batch_size, instrumenter)
28
+ def release_all_locks(redis, batch_size, instrumenter, logger)
26
29
  rel_start_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
27
30
  fully_release_all_locks(redis, batch_size) => { ok:, result: }
28
31
  time_at = Time.now.to_i
@@ -54,26 +57,28 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
54
57
  # @api private
55
58
  # @since 0.1.0
56
59
  def fully_release_all_locks(redis, batch_size)
57
- result = redis.pipelined do |pipeline|
58
- # Step A: release all queus and their related locks
59
- redis.scan(
60
- 'MATCH',
61
- RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
62
- count: batch_size
63
- ) do |lock_queue|
64
- # TODO: reduce unnecessary iterations
65
- pipeline.call('ZREMRANGEBYSCORE', lock_queue, '-inf', '+inf')
66
- pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue), '0')
67
- end
60
+ result = redis.with do |rconn|
61
+ rconn.pipelined do |pipeline|
62
+ # Step A: release all queus and their related locks
63
+ rconn.scan(
64
+ 'MATCH',
65
+ RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
66
+ count: batch_size
67
+ ) do |lock_queue|
68
+ # TODO: reduce unnecessary iterations
69
+ pipeline.call('ZREMRANGEBYSCORE', lock_queue, '-inf', '+inf')
70
+ pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue), '0')
71
+ end
68
72
 
69
- # Step B: release all locks
70
- redis.scan(
71
- 'MATCH',
72
- RedisQueuedLocks::Resource::LOCK_PATTERN,
73
- count: batch_size
74
- ) do |lock_key|
75
- # TODO: reduce unnecessary iterations
76
- pipeline.call('EXPIRE', lock_key, '0')
73
+ # Step B: release all locks
74
+ rconn.scan(
75
+ 'MATCH',
76
+ RedisQueuedLocks::Resource::LOCK_PATTERN,
77
+ count: batch_size
78
+ ) do |lock_key|
79
+ # TODO: reduce unnecessary iterations
80
+ pipeline.call('EXPIRE', lock_key, '0')
81
+ end
77
82
  end
78
83
  end
79
84
 
@@ -20,12 +20,15 @@ module RedisQueuedLocks::Acquier::ReleaseLock
20
20
  # The lock name that should be released.
21
21
  # @param isntrumenter [#notify]
22
22
  # See RedisQueuedLocks::Instrument::ActiveSupport for example.
23
+ # @param logger [#debug]
24
+ # - Logger object used from `configuration` layer (see config[:logger]);
25
+ # - See RedisQueuedLocks::Logging::VoidLogger for example;
23
26
  # @return [RedisQueuedLocks::Data,Hash<Symbol,Any>]
24
27
  # Format: { ok: true/false, result: Hash<Symbil,Numeric|String> }
25
28
  #
26
29
  # @api private
27
30
  # @since 0.1.0
28
- def release_lock(redis, lock_name, instrumenter)
31
+ def release_lock(redis, lock_name, instrumenter, logger)
29
32
  lock_key = RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
30
33
  lock_key_queue = RedisQueuedLocks::Resource.prepare_lock_queue(lock_name)
31
34
 
@@ -62,9 +65,11 @@ module RedisQueuedLocks::Acquier::ReleaseLock
62
65
  # @api private
63
66
  # @since 0.1.0
64
67
  def fully_release_lock(redis, lock_key, lock_key_queue)
65
- result = redis.multi do |transact|
66
- transact.call('ZREMRANGEBYSCORE', lock_key_queue, '-inf', '+inf')
67
- transact.call('EXPIRE', lock_key, '0')
68
+ result = redis.with do |rconn|
69
+ rconn.multi do |transact|
70
+ transact.call('ZREMRANGEBYSCORE', lock_key_queue, '-inf', '+inf')
71
+ transact.call('EXPIRE', lock_key, '0')
72
+ end
68
73
  end
69
74
 
70
75
  RedisQueuedLocks::Data[ok: true, result:]
@@ -9,18 +9,16 @@ class RedisQueuedLocks::Client
9
9
 
10
10
  configuration do
11
11
  setting :retry_count, 3
12
- setting :retry_delay, 200 # NOTE: milliseconds
13
- setting :retry_jitter, 25 # NOTE: milliseconds
14
- setting :try_to_lock_timeout, 10 # NOTE: seconds
15
- setting :default_lock_ttl, 5_000 # NOTE: milliseconds
16
- setting :default_queue_ttl, 15 # NOTE: seconds
12
+ setting :retry_delay, 200 # NOTE: in milliseconds
13
+ setting :retry_jitter, 25 # NOTE: in milliseconds
14
+ setting :try_to_lock_timeout, 10 # NOTE: in seconds
15
+ setting :default_lock_ttl, 5_000 # NOTE: in milliseconds
16
+ setting :default_queue_ttl, 15 # NOTE: in seconds
17
17
  setting :lock_release_batch_size, 100
18
18
  setting :key_extraction_batch_size, 500
19
19
  setting :instrumenter, RedisQueuedLocks::Instrument::VoidNotifier
20
20
  setting :uniq_identifier, -> { RedisQueuedLocks::Resource.calc_uniq_identity }
21
-
22
- # TODO: setting :logger, Logger.new(IO::NULL)
23
- # TODO: setting :debug, true/false
21
+ setting :logger, RedisQueuedLocks::Logging::VoidLogger
24
22
 
25
23
  validate('retry_count') { |val| val == nil || (val.is_a?(::Integer) && val >= 0) }
26
24
  validate('retry_delay') { |val| val.is_a?(::Integer) && val >= 0 }
@@ -30,6 +28,7 @@ class RedisQueuedLocks::Client
30
28
  validate('default_queue_ttl', :integer)
31
29
  validate('lock_release_batch_size', :integer)
32
30
  validate('instrumenter') { |val| RedisQueuedLocks::Instrument.valid_interface?(val) }
31
+ validate('logger') { |val| RedisQueuedLocks::Logging.valid_interface?(val) }
33
32
  validate('uniq_identifier', :proc)
34
33
  end
35
34
 
@@ -136,6 +135,7 @@ class RedisQueuedLocks::Client
136
135
  identity:,
137
136
  fail_fast:,
138
137
  metadata:,
138
+ logger: config[:logger],
139
139
  &block
140
140
  )
141
141
  end
@@ -186,7 +186,8 @@ class RedisQueuedLocks::Client
186
186
  RedisQueuedLocks::Acquier::ReleaseLock.release_lock(
187
187
  redis_client,
188
188
  lock_name,
189
- config[:instrumenter]
189
+ config[:instrumenter],
190
+ config[:logger]
190
191
  )
191
192
  end
192
193
 
@@ -233,7 +234,12 @@ class RedisQueuedLocks::Client
233
234
  # @api public
234
235
  # @since 0.1.0
235
236
  def extend_lock_ttl(lock_name, milliseconds)
236
- RedisQueuedLocks::Acquier::ExtendLockTTL.extend_lock_ttl(redis_client, lock_name)
237
+ RedisQueuedLocks::Acquier::ExtendLockTTL.extend_lock_ttl(
238
+ redis_client,
239
+ lock_name,
240
+ milliseconds,
241
+ config[:logger]
242
+ )
237
243
  end
238
244
 
239
245
  # @option batch_size [Integer]
@@ -246,7 +252,8 @@ class RedisQueuedLocks::Client
246
252
  RedisQueuedLocks::Acquier::ReleaseAllLocks.release_all_locks(
247
253
  redis_client,
248
254
  batch_size,
249
- config[:instrumenter]
255
+ config[:instrumenter],
256
+ config[:logger]
250
257
  )
251
258
  end
252
259
 
@@ -8,7 +8,7 @@ module RedisQueuedLocks::Instrument::ActiveSupport
8
8
  # @param payload [Hash<String|Symbol,Any>]
9
9
  # @return [void]
10
10
  #
11
- # @api private
11
+ # @api public
12
12
  # @since 0.1.0
13
13
  def notify(event, payload = {})
14
14
  ::ActiveSupport::Notifications.instrument(event, payload)
@@ -8,7 +8,7 @@ module RedisQueuedLocks::Instrument::VoidNotifier
8
8
  # @param payload [Hash<String|Symbol,Any>]
9
9
  # @return [void]
10
10
  #
11
- # @api private
11
+ # @api public
12
12
  # @since 0.1.0
13
13
  def notify(event, payload = {}); end
14
14
  end
@@ -25,10 +25,10 @@ module RedisQueuedLocks::Instrument
25
25
  m_obj = instrumenter.method(:notify)
26
26
  m_sig = m_obj.parameters
27
27
 
28
- f_prm = m_sig[0][0]
29
- s_prm = m_sig[1][0]
30
-
31
28
  if m_sig.size == 2
29
+ f_prm = m_sig[0][0]
30
+ s_prm = m_sig[1][0]
31
+
32
32
  # rubocop:disable Layout/MultilineOperationIndentation
33
33
  # NOTE: check the signature vairants
34
34
  f_prm == :req && s_prm == :req ||
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api public
4
+ # @since 0.1.0
5
+ module RedisQueuedLocks::Logging::VoidLogger
6
+ class << self
7
+ # @api public
8
+ # @since 0.1.0
9
+ def warn(progname = nil, &block); end
10
+
11
+ # @api public
12
+ # @since 0.1.0
13
+ def unknown(progname = nil, &block); end
14
+
15
+ # @api public
16
+ # @since 0.1.0
17
+ def log(progname = nil, &block); end
18
+
19
+ # @api public
20
+ # @since 0.1.0
21
+ def info(progname = nil, &block); end
22
+
23
+ # @api public
24
+ # @since 0.1.0
25
+ def error(progname = nil, &block); end
26
+
27
+ # @api public
28
+ # @since 0.1.0
29
+ def fatal(progname = nil, &block); end
30
+
31
+ # @api public
32
+ # @since 0.1.0
33
+ def debug(progname = nil, &block); end
34
+
35
+ # @api public
36
+ # @since 0.1.0
37
+ def add(*, &block); end
38
+
39
+ # @api public
40
+ # @since 0.1.0
41
+ def <<(message); end
42
+ end
43
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api public
4
+ # @since 0.1.0
5
+ module RedisQueuedLocks::Logging
6
+ require_relative 'logging/void_logger'
7
+
8
+ class << self
9
+ # @param logger [#debug]
10
+ # @return [Boolean]
11
+ #
12
+ # @api public
13
+ # @since 0.1.0
14
+ def valid_interface?(logger)
15
+ return true if logger.is_a?(::Logger)
16
+
17
+ # NOTE: should provide `#debug` method.
18
+ return false unless logger.respond_to?(:debug)
19
+
20
+ # NOTE:
21
+ # `#debug` method should have appropriated signature `(progname, &block)`
22
+ # Required method signature (progname, &block):
23
+ # => [[:opt, :progname], [:block, :block]]
24
+ # => [[:req, :progname], [:block, :block]]
25
+ # => [[:opt, :progname]]
26
+ # => [[:req, :progname]]
27
+ # => [[:rest], [:block, :block]]
28
+ # => [[:rest]]
29
+
30
+ m_obj = logger.method(:debug)
31
+ m_sig = m_obj.parameters
32
+
33
+ if m_sig.size == 2
34
+ # => [[:opt, :progname], [:block, :block]]
35
+ # => [[:req, :progname], [:block, :block]]
36
+ # => [[:rest], [:block, :block]]
37
+ f_prm = m_sig[0][0]
38
+ s_prm = m_sig[1][0]
39
+
40
+ # rubocop:disable Layout/MultilineOperationIndentation
41
+ f_prm == :opt && s_prm == :block ||
42
+ f_prm == :req && s_prm == :block ||
43
+ f_prm == :rest && s_prm == :block
44
+ # rubocop:enable Layout/MultilineOperationIndentation
45
+ elsif m_sig.size == 1
46
+ # => [[:opt, :progname]]
47
+ # => [[:req, :progname]]
48
+ # => [[:rest]]
49
+ prm = m_sig[0][0]
50
+
51
+ prm == :opt || prm == :req || prm == :rest
52
+ else
53
+ false
54
+ end
55
+ end
56
+ end
57
+ end
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 0.0.21
9
- VERSION = '0.0.21'
8
+ # @version 0.0.23
9
+ VERSION = '0.0.23'
10
10
  end
@@ -4,6 +4,7 @@ require 'redis-client'
4
4
  require 'qonfig'
5
5
  require 'timeout'
6
6
  require 'securerandom'
7
+ require 'logger'
7
8
 
8
9
  # @api public
9
10
  # @since 0.1.0
@@ -11,6 +12,7 @@ module RedisQueuedLocks
11
12
  require_relative 'redis_queued_locks/version'
12
13
  require_relative 'redis_queued_locks/errors'
13
14
  require_relative 'redis_queued_locks/utilities'
15
+ require_relative 'redis_queued_locks/logging'
14
16
  require_relative 'redis_queued_locks/data'
15
17
  require_relative 'redis_queued_locks/debugger'
16
18
  require_relative 'redis_queued_locks/resource'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis_queued_locks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.21
4
+ version: 0.0.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-03-19 00:00:00.000000000 Z
11
+ date: 2024-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -79,6 +79,8 @@ files:
79
79
  - lib/redis_queued_locks/instrument.rb
80
80
  - lib/redis_queued_locks/instrument/active_support.rb
81
81
  - lib/redis_queued_locks/instrument/void_notifier.rb
82
+ - lib/redis_queued_locks/logging.rb
83
+ - lib/redis_queued_locks/logging/void_logger.rb
82
84
  - lib/redis_queued_locks/resource.rb
83
85
  - lib/redis_queued_locks/utilities.rb
84
86
  - lib/redis_queued_locks/version.rb