redis_queued_locks 0.0.27 → 0.0.29

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 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