redis_queued_locks 0.0.27 → 0.0.29

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: f9d7bd4d48e34f520e1e7675030a15a346eca0f89429b500b7937ef36cf8107b
4
- data.tar.gz: 7c331e2a5d728a78ce8009bc6d1e97621bab61ad336683e45b52063f2fff1715
3
+ metadata.gz: 3137184f5a89b895fe347d8080957e08a57974e91d5a68189d0eb157afd7c3eb
4
+ data.tar.gz: 7b059f5de5cde2af1634eed3aeae540afd7edfc5ba564781789649098e53e0a1
5
5
  SHA512:
6
- metadata.gz: 33491d78f0a847f90ed27792ae3edd90388d78fb4098cd6eae00b85ea13c5353523f252f1cf83e01918bce502b3ad5735825e5f642122b0a15f6d301df12f5f1
7
- data.tar.gz: a18017f9c1bc2559fbd6c723ce1fccc99c7041f6952e535dba31e14a5029abf43c215c6abcd0aaf514d4d670614882255e2b356c3c214a83b77265a826fe16e0
6
+ metadata.gz: 4821b6c0fc142d90fae314aa11c92069be9223cc4a61be077ff6e19278fae97f920d31b590c5688f3614159a2f5c9d9d1792d8a6aee50f51d7b24f83ea1a9298
7
+ data.tar.gz: 80a0de485f263459c52d230cc0e604d0c742db235a6a9b57053cc84001e9e146cfc35b9c31aff5e1ef5a2796692559f95fe94db39350720277fb219ef95a2685
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.29] - 2024-03-23
4
+ ### Added
5
+ - Logging: more detailed logs to `RedisQueuedLocks::Acquier::AcquireLock::TryToLock`;
6
+
7
+ ## [0.0.28] - 2024-03-21
8
+ ### Added
9
+ - Logging: added `acq_id` to every log message;
10
+ - Logging: updated documentation;
11
+
3
12
  ## [0.0.27] - 2024-03-21
4
13
  ### Changed
5
14
  - Better acquier position accuracy: acquier position in lock queue
data/README.md CHANGED
@@ -139,15 +139,18 @@ clinet = RedisQueuedLocks::Client.new(redis_client) do |config|
139
139
  # - the logger object;
140
140
  # - should implement `debug(progname = nil, &block)` (minimal requirement) or be an instance of Ruby's `::Logger` class/subclass;
141
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'"
142
+ # - start of lock obtaining: "[redis_queud_locks.start_lock_obtaining] lock_key => 'rql:lock:your_lock' acq_id => 'rql:acq:54307/3620/3640/3540/c1799ede93'"
143
+ # - finish of the lock obtaining: "[redis_queued_locks.lock_obtained] lock_key => 'rql:lock:your_lock' acq_id => 'rql:acq:54307/3620/3640/3540/c1799ede93' acq_time => 123.456 (ms)"
144
+ # - start of the lock expiration after `yield`: "[redis_queud_locks.expire_lock] lock_key => 'rql:lock:your_lock' acq_id => 'rql:acq:54307/3620/3640/3540/c1799ede93'"
145
145
  # - by default uses VoidLogger that does nothing;
146
146
  config.logger = RedisQueuedLocks::Logging::VoidLogger
147
147
 
148
148
  # (default: false)
149
- # - should be logged the each try of lock acquiring (a lot of logs can be generated depending on your retry configurations);
150
- # - if logger is not cofnigured this option does not lead to any effect;
149
+ # - should be logged the each internal try-retry lock acquire (a lot of logs can be generated depending on your retry configurations);
150
+ # - it adds 2 cases to the log in addition to the existing 3:
151
+ # - start of a try: "[redis_queud_locks.try_lock_start] lock_key => 'rql:lock:your_lock' acq_id => 'rql:acq:54307/3620/3640/3540/c1799ede93'"
152
+ # - redis connection is feteched from the pool after "start of a try": "[redis_queued_locks.try_lock_rconn_fetched] lock_key => 'rql:lock:your_lock' acq_id => 'rql:acq:54307/3620/3640/3540/c1799ede93'"
153
+ # - if logger is not configured this option does not lead to any effect;
151
154
  config.log_lock_try = false
152
155
  end
153
156
  ```
@@ -597,23 +600,21 @@ Detalized event semantics and payload structure:
597
600
 
598
601
  ## Roadmap
599
602
 
600
- - **Major**
601
- - Semantic Error objects for unexpected Redis errors;
602
- - `100%` test coverage;
603
- - per-block-holding-the-lock sidecar `Ractor` and `in progress queue` in RedisDB that will extend
604
- the acquired lock for long-running blocks of code (that invoked "under" the lock
605
- whose ttl may expire before the block execution completes);
606
- - an ability to add custom metadata to the lock and an ability to read this data;
607
- - lock prioritization;
608
- - support for LIFO strategy;
609
- - structured logging;
610
- - **Minor**
611
- - GitHub Actions CI;
612
- - `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
613
- - better code stylization and interesting refactorings;
614
- - lock queue expiration (dead queue cleanup);
615
- - support for `Dragonfly` DB backend;
616
- - support for `Garnet` DB backend;
603
+ - Semantic Error objects for unexpected Redis errors;
604
+ - `100%` test coverage;
605
+ - per-block-holding-the-lock sidecar `Ractor` and `in progress queue` in RedisDB that will extend
606
+ the acquired lock for long-running blocks of code (that invoked "under" the lock
607
+ whose ttl may expire before the block execution completes);
608
+ - an ability to add custom metadata to the lock and an ability to read this data;
609
+ - lock prioritization;
610
+ - support for LIFO strategy;
611
+ - structured logging;
612
+ - GitHub Actions CI;
613
+ - `RedisQueuedLocks::Acquier::Try.try_to_lock` - detailed successful result analization;
614
+ - better code stylization and interesting refactorings;
615
+ - dead queue cleanup;
616
+ - support for `Dragonfly` DB backend;
617
+ - support for `Garnet` DB backend;
617
618
 
618
619
  ---
619
620
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
- # rubocop:disable Metrics/ModuleLength
5
+ # rubocop:disable Metrics/ModuleLength, Metrics/BlockNesting
6
6
  module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
7
7
  # @since 0.1.0
8
8
  extend RedisQueuedLocks::Utilities
@@ -41,7 +41,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
41
41
  if log_lock_try
42
42
  run_non_critical do
43
43
  logger.debug(
44
- "[redis_queued_locks.try_lock_start] " \
44
+ "[redis_queued_locks.try_lock.start] " \
45
45
  "lock_key => '#{lock_key}' " \
46
46
  "acq_id => '#{acquier_id}'"
47
47
  )
@@ -53,7 +53,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
53
53
  if log_lock_try
54
54
  run_non_critical do
55
55
  logger.debug(
56
- "[redis_queued_locks.try_lock_rconn_fetched] " \
56
+ "[redis_queued_locks.try_lock.rconn_fetched] " \
57
57
  "lock_key => '#{lock_key}' " \
58
58
  "acq_id => '#{acquier_id}'"
59
59
  )
@@ -63,12 +63,22 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
63
63
  rconn.multi(watch: [lock_key]) do |transact|
64
64
  # Fast-Step X0: fail-fast check
65
65
  if fail_fast && rconn.call('HGET', lock_key, 'acq_id')
66
- # Fast-Step X1: is lock already obtained. fail fast - no try.
66
+ # Fast-Step X1: is lock already obtained. fail fast leads to "no try".
67
67
  inter_result = :fail_fast_no_try
68
68
  else
69
69
  # Step 1: add an acquier to the lock acquirement queue
70
70
  res = rconn.call('ZADD', lock_key_queue, 'NX', acquier_position, acquier_id)
71
71
 
72
+ if log_lock_try
73
+ run_non_critical do
74
+ logger.debug(
75
+ "[redis_queued_locks.try_lock.acq_added_to_queue] " \
76
+ "lock_key => '#{lock_key}' " \
77
+ "acq_id => '#{acquier_id}'"
78
+ )
79
+ end
80
+ end
81
+
72
82
  RedisQueuedLocks.debug(
73
83
  "Step №1: добавление в очередь (#{acquier_id}). [ZADD to the queue: #{res}]"
74
84
  )
@@ -81,6 +91,16 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
81
91
  RedisQueuedLocks::Resource.acquier_dead_score(queue_ttl)
82
92
  )
83
93
 
94
+ if log_lock_try
95
+ run_non_critical do
96
+ logger.debug(
97
+ "[redis_queued_locks.try_lock.remove_expired_acqs] " \
98
+ "lock_key => '#{lock_key}' " \
99
+ "acq_id => '#{acquier_id}'"
100
+ )
101
+ end
102
+ end
103
+
84
104
  RedisQueuedLocks.debug(
85
105
  "Step №2: дропаем из очереди просроченных ожидающих. [ZREMRANGE: #{res}]"
86
106
  )
@@ -88,6 +108,17 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
88
108
  # Step 3: get the actual acquier waiting in the queue
89
109
  waiting_acquier = Array(rconn.call('ZRANGE', lock_key_queue, '0', '0')).first
90
110
 
111
+ if log_lock_try
112
+ run_non_critical do
113
+ logger.debug(
114
+ "[redis_queued_locks.try_lock.get_first_from_queue] " \
115
+ "lock_key => '#{lock_key}' " \
116
+ "acq_id => '#{acquier_id}' " \
117
+ "first_acq_id_in_queue => '#{waiting_acquier}'"
118
+ )
119
+ end
120
+ end
121
+
91
122
  RedisQueuedLocks.debug(
92
123
  "Step №3: какой процесс в очереди сейчас ждет. " \
93
124
  "[ZRANGE <следующий процесс>: #{waiting_acquier} :: <текущий процесс>: #{acquier_id}]"
@@ -97,6 +128,17 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
97
128
  unless waiting_acquier == acquier_id
98
129
  # Step ROLLBACK 1.1: our time hasn't come yet. retry!
99
130
 
131
+ if log_lock_try
132
+ run_non_critical do
133
+ logger.debug(
134
+ "[redis_queued_locks.try_lock.exit__no_first] " \
135
+ "lock_key => '#{lock_key}' " \
136
+ "acq_id => '#{acquier_id}' " \
137
+ "first_acq_id_in_queue => '#{waiting_acquier}'"
138
+ )
139
+ end
140
+ end
141
+
100
142
  RedisQueuedLocks.debug(
101
143
  "Step ROLLBACK №1: не одинаковые ключи. выходим. " \
102
144
  "[Ждет: #{waiting_acquier}. А нужен: #{acquier_id}]"
@@ -120,6 +162,18 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
120
162
  if locked_by_acquier
121
163
  # Step ROLLBACK 2: required lock is stil acquired. retry!
122
164
 
165
+ if log_lock_try
166
+ run_non_critical do
167
+ logger.debug(
168
+ "[redis_queued_locks.try_lock.exit__still_obtained] " \
169
+ "lock_key => '#{lock_key}' " \
170
+ "acq_id => '#{acquier_id}' " \
171
+ "first_acq_id_in_queue => '#{waiting_acquier}' " \
172
+ "locked_by_acq_id => '#{locked_by_acquier}'"
173
+ )
174
+ end
175
+ end
176
+
123
177
  RedisQueuedLocks.debug(
124
178
  "Step ROLLBACK №2: Ключ уже занят. Ничего не делаем. " \
125
179
  "[Занят процессом: #{locked_by_acquier}]"
@@ -153,6 +207,16 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
153
207
 
154
208
  # Step 6.3: set the lock expiration time in order to prevent "infinite locks"
155
209
  transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
210
+
211
+ if log_lock_try
212
+ run_non_critical do
213
+ logger.debug(
214
+ "[redis_queued_locks.try_lock.run__free_to_acquire] " \
215
+ "lock_key => '#{lock_key}' " \
216
+ "acq_id => '#{acquier_id}'"
217
+ )
218
+ end
219
+ end
156
220
  end
157
221
  end
158
222
  end
@@ -210,4 +274,4 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
210
274
  RedisQueuedLocks::Data[ok: true, result: result]
211
275
  end
212
276
  end
213
- # rubocop:enable Metrics/ModuleLength
277
+ # rubocop:enable Metrics/ModuleLength, Metrics/BlockNesting
@@ -9,6 +9,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
9
9
  # @param redis [RedisClient] Redis connection manager.
10
10
  # @param logger [::Logger,#debug] Logger object.
11
11
  # @param lock_key [String] Lock key to be expired.
12
+ # @param acquier_id [String] Acquier identifier.
12
13
  # @param timed [Boolean] Should the lock be wrapped by Tiemlout with with lock's ttl
13
14
  # @param ttl_shift [Float] Lock's TTL shifting. Should affect block's ttl. In millisecodns.
14
15
  # @param ttl [Integer,NilClass] Lock's time to live (in ms). Nil means "without timeout".
@@ -17,7 +18,7 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
17
18
  #
18
19
  # @api private
19
20
  # @since 0.1.0
20
- def yield_with_expire(redis, logger, lock_key, timed, ttl_shift, ttl, &block)
21
+ def yield_with_expire(redis, logger, lock_key, acquier_id, timed, ttl_shift, ttl, &block)
21
22
  if block_given?
22
23
  if timed && ttl != nil
23
24
  timeout = ((ttl - ttl_shift) / 1000.0).yield_self { |time| (time < 0) ? 0.0 : time }
@@ -28,7 +29,11 @@ module RedisQueuedLocks::Acquier::AcquireLock::YieldWithExpire
28
29
  end
29
30
  ensure
30
31
  run_non_critical do
31
- logger.debug("[redis_queued_locks.expire_lock] lock_key => '#{lock_key}'")
32
+ logger.debug(
33
+ "[redis_queued_locks.expire_lock] " \
34
+ "lock_key => '#{lock_key}' " \
35
+ "acq_id => '#{acquier_id}'"
36
+ )
32
37
  end
33
38
  redis.call('EXPIRE', lock_key, '0')
34
39
  end
@@ -176,7 +176,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
176
176
  logger.debug(
177
177
  "[redis_queued_locks.lock_obtained] " \
178
178
  "lock_key => '#{result[:lock_key]}' " \
179
- "acq_id: => '#{acquier_id}' " \
179
+ "acq_id => '#{acquier_id}' " \
180
180
  "acq_time => #{acq_time} (ms)"
181
181
  )
182
182
  end
@@ -255,7 +255,7 @@ module RedisQueuedLocks::Acquier::AcquireLock
255
255
  begin
256
256
  yield_time = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
257
257
  ttl_shift = ((yield_time - acq_process[:acq_end_time]) * 1000).ceil(2)
258
- yield_with_expire(redis, logger, lock_key, timed, ttl_shift, ttl, &block)
258
+ yield_with_expire(redis, logger, lock_key, acquier_id, timed, ttl_shift, ttl, &block)
259
259
  ensure
260
260
  acq_process[:rel_time] = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
261
261
  acq_process[:hold_time] = (
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 0.0.27
9
- VERSION = '0.0.27'
8
+ # @version 0.0.29
9
+ VERSION = '0.0.29'
10
10
  end
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.27
4
+ version: 0.0.29
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-21 00:00:00.000000000 Z
11
+ date: 2024-03-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client