redis_queued_locks 1.15.1 → 1.16.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f196a3e198011c805f10df6ba3c2110471b85e0f7e3629a8d6c0594329c6a61c
4
- data.tar.gz: 00d1067ee165b563380b0e2061af8c73e37466e8a55b2f43c8f119069fd90b39
3
+ metadata.gz: 38fff24c73bc7a295a40e9cf9bd2b83ab59c9881690992ff8f697f01332423e6
4
+ data.tar.gz: abd042d5ef25b0d8745d4551ace657fbf622aab09afbc6a2162a8fea9100143a
5
5
  SHA512:
6
- metadata.gz: 6b6066096f58daeafab9c6c06f815628be1d2b32a56374f1fc3d1d6cdcff4f7aba6271d20efc16c0439084e629b6867e66eeb58d0091454b5441e29310fcb83b
7
- data.tar.gz: 43751ba0011d662474c47ad014620d2b43d4555f04ae41f1a23ac1f7072aa20736a430e5a7db2a494449fe3245331e48bcf8453b91ba56c4ceba08dafaf5ed42
6
+ metadata.gz: c06c9a81b8809bc67a7003d479efb3aba3f57a595fd05fe8e53ac94bc0150023e0b5249c1f64130b224c31e3bb08c243679e763f916ab7f3baac2feaa05a49eb
7
+ data.tar.gz: 869b6a0a2c2e6249a944f62f258772ca2f3346fe613597bc97090adaa2d7867f1ec2bc936690b2822f06e6d3842880a9bec601af0ff06c57a0fab008b00878be
data/CHANGELOG.md CHANGED
@@ -1,4 +1,14 @@
1
- ## [Unreleased]
1
+ ## [1.16.0]
2
+ ### Added
3
+ - Brand new Feature **"Lock series of locks"**
4
+ - (*Proof of Concpet*-based implementation at the moment)
5
+ - lock series of lock simultainously (and release them all at the finish of your the invocation of block of code under the obtained locks);
6
+ - exmaple: `client.lock_series("a", "b", "c")` (or in exceptional-based way `client.lock_series!("a", "b", "c")`;
7
+ ### Changed
8
+ - YARDOC documentation updates in order to prearet to the 100% yardoc-coverage;
9
+ - updated dev deps;
10
+ ### Fixed
11
+ - fixed some yardoc documentation typos;
2
12
 
3
13
  ## [1.15.1] - 2025-10-22
4
14
  ### Fixed
data/README.md CHANGED
@@ -23,6 +23,8 @@ Provides flexible invocation flow, parametrized limits (lock request ttl, lock t
23
23
  - [Usage](#usage)
24
24
  - [lock](#lock---obtain-a-lock)
25
25
  - [lock!](#lock---exceptional-lock-obtaining)
26
+ - [lock_series (PoC)](#lock_series---poc-acquire-a-series-of-locks)
27
+ - [lock_series! (PoC)](#lock_series---poc-exceptional-lock_series)
26
28
  - [lock_info](#lock_info)
27
29
  - [queue_info](#queue_info)
28
30
  - [locked?](#locked)
@@ -850,6 +852,74 @@ See `#lock` method [documentation](#lock---obtain-a-lock).
850
852
 
851
853
  ---
852
854
 
855
+ #### #lock_series - (PoC) acquire a series of locks
856
+
857
+ > (IMPORTANT!) "Proof of Concept" realization. Will be reworked in the future (very-very soon)
858
+
859
+ `lock_series` - acquire a series of locks simultaniously and hold them all while your block of code is executing
860
+ or (if block is not passed) while all locks will not expire.
861
+
862
+ **IMPORTANT DETAILS AND LIMITATIONS**:
863
+ - (this behavior will be reworked with another algorithm);
864
+ - each lock will be released with the next formula:
865
+ - `lock position with reverse numbering` * `ttl`
866
+ - (positions exmaple: ["a", "b", "c"] => "a" position is 3, "b" position is 2, "c" position is 1);
867
+ - this means taht in case of 3 locks (`lock_series("x", "y", "z", ttl: 15_000`) with `5 seconds TTL` your locks will be with the next TTL:
868
+ - `"x"` - 15 seconds
869
+ - `"y"` - 10 seconds
870
+ - `"z"` - 5 seconds
871
+ - when your block of code has completed their execution - all locks will be released immediatly;
872
+ - if you did not pass a block - all locks will be released with their forumla-based own TTL described above;
873
+ - **HOW WILL THIS CASE WORK IN FUTURE RELESAE**: all locks will have the same TTL (`5 seconds` for our example, not `15/10/5 seconds`);
874
+
875
+ ###### How to use:
876
+
877
+ ```ruby
878
+ # typical logic-oriented way
879
+ client.lock_series("a", "b", "c") { ...some_code... }
880
+
881
+ # result-oriented way
882
+ detailed_result = client.lock_series('x', 'y', 'z', detailed_result: true) { 1 + 2 }
883
+ detailed_result # =>
884
+ {
885
+ yield_result: 3, # result of your block of code
886
+ locks_released_at: 1769506100.676884, # (instrumentation) release time, epoch
887
+ locks_acq_time: 7.0, # (instrumentation) time spent to acquire all locks, milliseconds
888
+ locks_hold_time: 0.65, # (instrumentation) lock hold period, milliseconds
889
+ lock_series: ["x", "y", "z"], # your locks
890
+ rql_lock_series: ["rql:lock:x", "rql:lock:y", "rql:lock:z"] # internal RQL lock-keys of your locks
891
+ }
892
+ ```
893
+
894
+ ###### New instrumentaiton events and logs:
895
+
896
+ ```ruby
897
+ # NEW instrumentation events (examples)
898
+ "redis_queued_locks.lock_series_hold_and_release" => {hold_time: 6.41, ttl: 5000, acq_id: "rql:acq:57147/1696/1704/1712/70ff69bc025ace02", hst_id: "rql:hst:57147/1696/1712/70ff69bc025ace02", ts: 1769505674.958648, lock_keys: ["rql:lock:a", "rql:lock:b"], acq_time: 2.35, instrument: nil}
899
+ "redis_queued_locks.lock_obtained" => {lock_key: "rql:lock:x", ttl: 15675, acq_id: "rql:acq:57685/1696/1704/1712/b5b8c38227c18e8a", hst_id: "rql:hst:57685/1696/1712/b5b8c38227c18e8a", ts: 1769505795.984393, acq_time: 4.15, instrument: nil}
900
+ ```
901
+
902
+ ```shell
903
+ # NEW logs (examples)
904
+ [redis_queued_locks.start_lock_series_obtaining] lock_keys => '["rql:lock:x", "rql:lock:y", "rql:lock:c"]'queue_ttl => 15 acq_id => 'rql:acq:57685/1696/1704/1712/b5b8c38227c18e8a' hst_id => 'rql:hst:57685/1696/1712/b5b8c38227c18e8a' acs_strat => 'queued'
905
+ [redis_queued_locks.lock_series_obtained] lock_keys => '["rql:lock:x", "rql:lock:y", "rql:lock:z"]' queue_ttl => 15 acq_id => 'rql:acq:57685/1696/1704/1712/b5b8c38227c18e8a' hst_id => 'rql:hst:57685/1696/1712/b5b8c38227c18e8a' acs_strat => 'queued' acq_time => 7.02 (ms)
906
+ [redis_queued_locks.expire_lock_series] lock_keys => '["rql:lock:x", "rql:lock:y", "rql:lock:z"]' queue_ttl => 15 acq_id => 'rql:acq:57685/1696/1704/1712/b5b8c38227c18e8a' hst_id => 'rql:hst:57685/1696/1712/b5b8c38227c18e8a' acs_strat => 'queued'
907
+ ```
908
+
909
+ ----
910
+
911
+ #### #lock_series! - (PoC) exceptional `lock_series`
912
+
913
+ > (IMPORTANT!) "Proof of Concept" realization. Will be reworked in the future (very-very soon)
914
+
915
+ For more details see `lock_series` [docs](#lock_series---poc-acquire-a-series-of-locks)
916
+
917
+ ```ruby
918
+ client.lock_series!("x", "y", "z")
919
+ ```
920
+
921
+ ----
922
+
853
923
  #### #lock_info
854
924
 
855
925
  <sup>\[[back to top](#usage)\]</sup>
@@ -2328,6 +2398,8 @@ Detalized event semantics and payload structure:
2328
2398
  - `write` - waits - `write`;
2329
2399
  - **write** mode is a default behavior for all RQL locks;
2330
2400
  - **Minor**:
2401
+ - an ability to return all insturmentation metrics from the `lock` invocation (and after block `yield`ing);
2402
+ - think about "thread priority" configuration :thinking:;
2331
2403
  - add `hst_id` to all methods that works with queues info;
2332
2404
  - try to return the `fiber object id` to the lock host identifier (we cant use fiber object id cuz `ObjectSpace` has no access to the fiber object space after the any ractor object initialization)
2333
2405
  - named RQL's threads (`Thread#name`) and RQL's ractors (`Ractor#name`) in order to have an ability to find and work with RQL's threads and ractors outside of RQL logic (stop threads before process forking, for example);
@@ -2358,6 +2430,8 @@ Detalized event semantics and payload structure:
2358
2430
  - **Research**: support for `Valkey` database backend (https://github.com/valkey-io/valkey) (https://valkey.io/);
2359
2431
  - **Research**: support for `Garnet` database backend (https://microsoft.github.io/) (https://github.com/microsoft/garnet);
2360
2432
  - add a library-level exception, when RQL-related key in Redis (required for its logic) has incompatible type (means: some other program uses our key with their own type and logic and RQL can't work properly);
2433
+ - yardoc docs with CI check (full doc coverage check);
2434
+
2361
2435
  ---
2362
2436
 
2363
2437
  ## Build and Develop
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- # @since 1.7.0
4
+ # @since 1.16.0
5
+ # rubocop:disable Metrics/ModuleLength
5
6
  module RedisQueuedLocks::Acquirer::AcquireLock::InstrVisitor
6
7
  class << self
7
8
  # @param instrumenter [#notify]
@@ -162,5 +163,27 @@ module RedisQueuedLocks::Acquirer::AcquireLock::InstrVisitor
162
163
  hold_time:, ttl:, acq_id:, hst_id:, ts:, lock_key:, acq_time:, instrument:
163
164
  }) rescue nil
164
165
  end
166
+
167
+ # NOTE: Lock Series PoC
168
+ # @api private
169
+ # @since 1.16.0
170
+ def lock_series_hold_and_release( # steep:ignore
171
+ instrumenter,
172
+ instr_sampled,
173
+ lock_keys,
174
+ ttl,
175
+ acq_id,
176
+ hst_id,
177
+ ts,
178
+ acq_time,
179
+ hold_time,
180
+ instrument
181
+ )
182
+ return unless instr_sampled
183
+ instrumenter.notify('redis_queued_locks.lock_series_hold_and_release', {
184
+ hold_time:, ttl:, acq_id:, hst_id:, ts:, lock_keys:, acq_time:, instrument:
185
+ }) # rescue nil
186
+ end
165
187
  end
166
188
  end
189
+ # rubocop:enable Metrics/ModuleLength
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- # @since 1.7.0
4
+ # @since 1.16.0
5
5
  # rubocop:disable Metrics/ModuleLength
6
6
  module RedisQueuedLocks::Acquirer::AcquireLock::LogVisitor
7
7
  # rubocop:disable Metrics/ClassLength
@@ -39,6 +39,30 @@ module RedisQueuedLocks::Acquirer::AcquireLock::LogVisitor
39
39
  end rescue nil
40
40
  end
41
41
 
42
+ # NOTE: Lock Series PoC
43
+ # @api private
44
+ # @since 1.16.0
45
+ def start_lock_series_obtaining( # steep:ignore
46
+ logger,
47
+ log_sampled,
48
+ lock_keys,
49
+ queue_ttl,
50
+ acquirer_id,
51
+ host_id,
52
+ access_strategy
53
+ )
54
+ return unless log_sampled
55
+
56
+ logger.debug do
57
+ "[redis_queued_locks.start_lock_series_obtaining] " \
58
+ "lock_keys => '#{lock_keys.inspect}'" \
59
+ "queue_ttl => #{queue_ttl} " \
60
+ "acq_id => '#{acquirer_id}' " \
61
+ "hst_id => '#{host_id}' " \
62
+ "acs_strat => '#{access_strategy}'"
63
+ end # rescue nil
64
+ end
65
+
42
66
  # @param logger [::Logger,#debug]
43
67
  # @param log_sampled [Boolean]
44
68
  # @param lock_key [String]
@@ -212,6 +236,32 @@ module RedisQueuedLocks::Acquirer::AcquireLock::LogVisitor
212
236
  "acq_time => #{acq_time} (ms)"
213
237
  end rescue nil
214
238
  end
239
+
240
+ # NOTE: Lock Series PoC
241
+ # @api private
242
+ # @since 1.16.0
243
+ def lock_series_obtained( # steep:ignore
244
+ logger,
245
+ log_sampled,
246
+ lock_keys,
247
+ queue_ttl,
248
+ acquirer_id,
249
+ host_id,
250
+ acq_time,
251
+ access_strategy
252
+ )
253
+ return unless log_sampled
254
+
255
+ logger.debug do
256
+ "[redis_queued_locks.lock_series_obtained] " \
257
+ "lock_keys => '#{lock_keys.inspect}' " \
258
+ "queue_ttl => #{queue_ttl} " \
259
+ "acq_id => '#{acquirer_id}' " \
260
+ "hst_id => '#{host_id}' " \
261
+ "acs_strat => '#{access_strategy}' " \
262
+ "acq_time => #{acq_time} (ms)"
263
+ end # rescue nil
264
+ end
215
265
  end
216
266
  # rubocop:enable Metrics/ClassLength
217
267
  end
@@ -17,7 +17,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::WithAcqTimeout
17
17
  # Add additional error data about lock queue and required lock to the timeout error or not.
18
18
  # @option on_timeout [Proc,NilClass]
19
19
  # Callback invoked on Timeout::Error.
20
- # @param block [Block] Custom logic that should be invoked under the obtained lock.
20
+ # @yield Custom logic that should be invoked under the obtained lock.
21
+ # @yieldreturn [Any]
21
22
  # @return [Any]
22
23
  #
23
24
  # @raise [RedisQueuedLocks::LockAcquirementIntermediateTimeoutError]
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # @api private
4
- # @since 1.7.0
4
+ # @since 1.16.0
5
5
  module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire::LogVisitor
6
6
  class << self
7
7
  # @param logger [::Logger,#debug]
@@ -37,6 +37,30 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire::LogVisitor
37
37
  end rescue nil
38
38
  end
39
39
 
40
+ # NOTE: Lock Series PoC
41
+ # @api private
42
+ # @since 1.16.0
43
+ def expire_lock_series( # steep:ignore
44
+ logger,
45
+ log_sampled,
46
+ lock_series,
47
+ queue_ttl,
48
+ acquirer_id,
49
+ host_id,
50
+ access_strategy
51
+ )
52
+ return unless log_sampled
53
+
54
+ logger.debug do
55
+ "[redis_queued_locks.expire_lock_series] " \
56
+ "lock_keys => '#{lock_series.inspect}' " \
57
+ "queue_ttl => #{queue_ttl} " \
58
+ "acq_id => '#{acquirer_id}' " \
59
+ "hst_id => '#{host_id}' " \
60
+ "acs_strat => '#{access_strategy}'"
61
+ end rescue nil
62
+ end
63
+
40
64
  # @param logger [::Logger,#debug]
41
65
  # @param log_sampled [Boolean]
42
66
  # @param lock_key [String]
@@ -32,7 +32,8 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
32
32
  # @param should_decrease [Boolean]
33
33
  # - Should decrease the lock TTL after the lock invocation;
34
34
  # - It is suitable for extendable reentrant locks;
35
- # @param block [Block] Custom logic that should be invoked under the obtained lock.
35
+ # @yield Custom logic that should be invoked under the obtained lock.
36
+ # @yieldreturn [Any]
36
37
  # @return [Any,NilClass] nil is returned when no block parametr is provided.
37
38
  #
38
39
  # @api private
@@ -122,7 +123,7 @@ module RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
122
123
  private
123
124
 
124
125
  # @param timeout [Float]
125
- # @parma lock_key [String]
126
+ # @param lock_key [String]
126
127
  # @param lock_ttl [Integer,NilClass]
127
128
  # @param acquirer_id [String]
128
129
  # @param host_id [String]
@@ -151,11 +151,11 @@ module RedisQueuedLocks::Acquirer::AcquireLock
151
151
  # - marks the method that everything should be instrumneted
152
152
  # despite the enabled instrumentation sampling;
153
153
  # - makes sense when instrumentation sampling is enabled;
154
- # @param [Block]
155
- # A block of code that should be executed after the successfully acquired lock.
154
+ # @yield A block of code that should be executed after the successfully acquired lock.
155
+ # @yieldreturn [Any]
156
156
  # @return [Hash<Symbol,Any>,yield]
157
157
  # - Format: { ok: true/false, result: Any }
158
- # - If block is given the result of block's yeld will be returned.
158
+ # - If block is given the result of block's yield will be returned.
159
159
  #
160
160
  # @api private
161
161
  # @since 1.0.0
@@ -0,0 +1,303 @@
1
+ # frozen_string_literal: true
2
+
3
+ # NOTE: Lock Series PoC
4
+ # steep:ignore
5
+ # rubocop:disable all
6
+ # @api private
7
+ # @since 1.16.0
8
+ module RedisQueuedLocks::Acquirer::LockSeriesPoC # steep:ignore
9
+ # @since 1.16.0
10
+ extend RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
11
+
12
+ class << self
13
+ # @api private
14
+ # @since 1.16.0
15
+ def lock_series_poc( # steep:ignore
16
+ redis,
17
+ lock_names,
18
+ detailed_result:,
19
+ process_id:,
20
+ thread_id:,
21
+ fiber_id:,
22
+ ractor_id:,
23
+ ttl:,
24
+ queue_ttl:,
25
+ timeout:,
26
+ timed:,
27
+ retry_count:,
28
+ retry_delay:,
29
+ retry_jitter:,
30
+ raise_errors:,
31
+ instrumenter:,
32
+ identity:,
33
+ fail_fast:,
34
+ meta:,
35
+ detailed_acq_timeout_error:,
36
+ instrument:,
37
+ logger:,
38
+ log_lock_try:,
39
+ conflict_strategy:,
40
+ read_write_mode:,
41
+ access_strategy:,
42
+ log_sampling_enabled:,
43
+ log_sampling_percent:,
44
+ log_sampler:,
45
+ log_sample_this:,
46
+ instr_sampling_enabled:,
47
+ instr_sampling_percent:,
48
+ instr_sampler:,
49
+ instr_sample_this:,
50
+ &block
51
+ )
52
+ case meta
53
+ when Hash, NilClass then nil
54
+ else
55
+ raise(
56
+ RedisQueuedLocks::ArgumentError,
57
+ "`:meta` argument should be a type of NilClass or Hash, got #{meta.class}."
58
+ )
59
+ end
60
+
61
+ if meta.is_a?(::Hash) && (meta.any? do |key, _value|
62
+ key == 'acq_id' ||
63
+ key == 'hst_id' ||
64
+ key == 'ts' ||
65
+ key == 'ini_ttl' ||
66
+ key == 'lock_key' ||
67
+ key == 'rem_ttl' ||
68
+ key == 'spc_ext_ttl' ||
69
+ key == 'spc_cnt' ||
70
+ key == 'l_spc_ext_ini_ttl' ||
71
+ key == 'l_spc_ext_ts' ||
72
+ key == 'l_spc_ts'
73
+ end)
74
+ raise(
75
+ RedisQueuedLocks::ArgumentError,
76
+ '`:meta` keys can not overlap reserved lock data keys ' \
77
+ '"acq_id", "hst_id", "ts", "ini_ttl", "lock_key", "rem_ttl", "spc_cnt", ' \
78
+ '"spc_ext_ttl", "l_spc_ext_ini_ttl", "l_spc_ext_ts", "l_spc_ts"'
79
+ )
80
+ end
81
+
82
+ locks_count = lock_names.size
83
+
84
+ lock_acquirement_operations = lock_names.each_with_index.map do |lock_name, position|
85
+ lock_ttl =
86
+ ttl * (locks_count - position) +
87
+ retry_delay * (locks_count - position) +
88
+ retry_jitter * (locks_count - position)
89
+
90
+ { lock_name: lock_name, ttl: lock_ttl }
91
+ end
92
+
93
+ log_sampled = RedisQueuedLocks::Logging.should_log?(
94
+ log_sampling_enabled,
95
+ log_sample_this,
96
+ log_sampling_percent,
97
+ log_sampler
98
+ )
99
+
100
+ instr_sampled = RedisQueuedLocks::Instrument.should_instrument?(
101
+ instr_sampling_enabled,
102
+ instr_sample_this,
103
+ instr_sampling_percent,
104
+ instr_sampler
105
+ )
106
+
107
+ lock_keys_for_instrumentation = lock_names.map do |lock_name|
108
+ RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
109
+ end
110
+
111
+ acquirer_id_for_instrumentation = RedisQueuedLocks::Resource.acquirer_identifier(
112
+ process_id,
113
+ thread_id,
114
+ fiber_id,
115
+ ractor_id,
116
+ identity
117
+ )
118
+
119
+ host_id_for_instrumentation = RedisQueuedLocks::Resource.host_identifier(
120
+ process_id,
121
+ thread_id,
122
+ ractor_id,
123
+ identity
124
+ )
125
+
126
+ RedisQueuedLocks::Acquirer::AcquireLock::LogVisitor.start_lock_series_obtaining( # steep:ignore
127
+ logger, log_sampled, lock_keys_for_instrumentation,
128
+ queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy
129
+ )
130
+
131
+ acq_start_time = RedisQueuedLocks::Utilities.clock_gettime
132
+ successfully_acquired_locks = [] # steep:ignore
133
+ failed_locks_and_errors = {} # steep:ignore
134
+ failed_on_lock = nil
135
+
136
+ lock_acquirement_operations.map do |lock_operation_options|
137
+ result =
138
+ begin
139
+ RedisQueuedLocks::Acquirer::AcquireLock.acquire_lock(
140
+ redis,
141
+ lock_operation_options[:lock_name],
142
+ process_id:,
143
+ thread_id:,
144
+ fiber_id:,
145
+ ractor_id:,
146
+ ttl: lock_operation_options[:ttl],
147
+ queue_ttl:,
148
+ timeout:,
149
+ timed:,
150
+ retry_count:,
151
+ retry_delay:,
152
+ retry_jitter:,
153
+ raise_errors:,
154
+ instrumenter:,
155
+ identity:,
156
+ fail_fast:,
157
+ meta:,
158
+ detailed_acq_timeout_error:,
159
+ instrument:,
160
+ logger:,
161
+ log_lock_try:,
162
+ conflict_strategy:,
163
+ read_write_mode:,
164
+ access_strategy:,
165
+ log_sampling_enabled:,
166
+ log_sampling_percent:,
167
+ log_sampler:,
168
+ log_sample_this:,
169
+ instr_sampling_enabled:,
170
+ instr_sampling_percent:,
171
+ instr_sampler:,
172
+ instr_sample_this:
173
+ )
174
+ rescue => error
175
+ if raise_errors && successfully_acquired_locks.any?
176
+ # NOTE: release all previously acquired locks if any next lock is already locked
177
+ successfully_acquired_locks.each do |operation_result|
178
+ lock_key = RedisQueuedLocks::Resource.prepare_lock_key(operation_result[:lock_name])
179
+ redis.with do |conn|
180
+ conn.multi(watch: [lock_key]) do |transact|
181
+ transact.call('DEL', lock_key)
182
+ end
183
+ end
184
+ end
185
+ end
186
+ raise(error)
187
+ end
188
+
189
+ if result[:ok]
190
+ successfully_acquired_locks << {
191
+ lock_name: lock_operation_options[:lock_name],
192
+ ok: result[:ok],
193
+ result: result[:result]
194
+ }
195
+ else
196
+ failed_on_lock = lock_operation_options[:lock_name]
197
+ failed_locks_and_errors[lock_operation_options[:lock_name]] = result
198
+ break
199
+ end
200
+ end
201
+
202
+ if (successfully_acquired_locks.size == lock_names.size && (successfully_acquired_locks.all? { |res| res[:ok] }))
203
+ acq_end_time = RedisQueuedLocks::Utilities.clock_gettime
204
+ acq_time = ((acq_end_time - acq_start_time) / 1_000.0).ceil(2)
205
+
206
+ RedisQueuedLocks::Acquirer::AcquireLock::LogVisitor.lock_series_obtained( # steep:ignore
207
+ logger, log_sampled, lock_keys_for_instrumentation,
208
+ queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation,
209
+ acq_time, access_strategy
210
+ )
211
+
212
+ yield_time = RedisQueuedLocks::Utilities.clock_gettime
213
+ ttl_shift = (
214
+ (yield_time - acq_end_time) / 1_000.0 -
215
+ RedisQueuedLocks::Resource::REDIS_TIMESHIFT_ERROR
216
+ ).ceil(2)
217
+
218
+ yield_result = yield_expire( # steep:ignore
219
+ redis,
220
+ logger,
221
+ lock_keys_for_instrumentation.last,
222
+ acquirer_id_for_instrumentation,
223
+ host_id_for_instrumentation,
224
+ access_strategy,
225
+ timed,
226
+ ttl_shift,
227
+ ttl,
228
+ queue_ttl,
229
+ meta,
230
+ log_sampled,
231
+ instr_sampled,
232
+ false, # should_expire (expire manually)
233
+ false, # should_decrease (expire manually)
234
+ &block
235
+ )
236
+
237
+ # expire locks manually
238
+ if block_given?
239
+ redis.with do |conn|
240
+ # use transaction in order to exclude any cross-locking during the group expiration
241
+ conn.multi(watch: lock_keys_for_instrumentation) do |transaction|
242
+ lock_keys_for_instrumentation.each do |lock_key|
243
+ transaction.call('EXPIRE', lock_key, '0')
244
+ end
245
+ end
246
+ end
247
+ end
248
+
249
+ rel_time = RedisQueuedLocks::Utilities.clock_gettime
250
+ hold_time = ((rel_time - acq_end_time) / 1_000.0).ceil(2)
251
+ ts = Time.now.to_f
252
+
253
+ RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire::LogVisitor.expire_lock_series( # steep:ignore
254
+ logger, log_sampled, lock_keys_for_instrumentation,
255
+ queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy
256
+ )
257
+
258
+ RedisQueuedLocks::Acquirer::AcquireLock::InstrVisitor.lock_series_hold_and_release( # steep:ignore
259
+ instrumenter,
260
+ instr_sampled,
261
+ lock_keys_for_instrumentation,
262
+ ttl,
263
+ acquirer_id_for_instrumentation,
264
+ host_id_for_instrumentation,
265
+ ts,
266
+ acq_time,
267
+ hold_time,
268
+ instrument
269
+ )
270
+
271
+ if detailed_result
272
+ {
273
+ yield_result: yield_result,
274
+ locks_released_at: ts,
275
+ locks_acq_time: acq_time,
276
+ locks_hold_time: hold_time,
277
+ lock_series: lock_names,
278
+ rql_lock_series: lock_keys_for_instrumentation
279
+ }
280
+ else
281
+ yield_result
282
+ end
283
+ else
284
+ acquired_locks = successfully_acquired_locks.map { |state| state[:lock_name] }
285
+ missing_locks = lock_names - acquired_locks
286
+
287
+ {
288
+ ok: false,
289
+ result: {
290
+ error: :failed_to_acquire_lock_series,
291
+ detailed_errors: failed_locks_and_errors,
292
+ lock_series: lock_names,
293
+ acquired_locks:,
294
+ missing_locks:,
295
+ failed_on_lock:,
296
+ }
297
+ }
298
+ end
299
+ end
300
+ end
301
+ end
302
+ # steep:ignore
303
+ # rubocop:enable all
@@ -42,7 +42,7 @@ module RedisQueuedLocks::Acquirer::Queues
42
42
  end
43
43
 
44
44
  # @param redis_client [RedisClient]
45
- # @param lock_queus [Set<String>]
45
+ # @param lock_queues [Set<String>]
46
46
  # @return [Set<Hash<Symbol,Any>>]
47
47
  #
48
48
  # @api private
@@ -18,7 +18,7 @@ module RedisQueuedLocks::Acquirer::ReleaseAllLocks
18
18
  # @param logger [::Logger,#debug]
19
19
  # - Logger object used from `configuration` layer (see config['logger']);
20
20
  # - See RedisQueuedLocks::Logging::VoidLogger for example;
21
- # @param isntrumenter [#notify]
21
+ # @param instrumenter [#notify]
22
22
  # See RedisQueuedLocks::Instrument::ActiveSupport for example.
23
23
  # @param instrument [NilClass,Any]
24
24
  # - Custom instrumentation data wich will be passed to the instrumenter's payload
@@ -18,7 +18,7 @@ module RedisQueuedLocks::Acquirer::ReleaseLock
18
18
  # Redis connection client.
19
19
  # @param lock_name [String]
20
20
  # The lock name that should be released.
21
- # @param isntrumenter [#notify]
21
+ # @param instrumenter [#notify]
22
22
  # See RedisQueuedLocks::Instrument::ActiveSupport for example.
23
23
  # @param logger [::Logger,#debug]
24
24
  # - Logger object used from `configuration` layer (see config['logger']);
@@ -24,7 +24,7 @@ module RedisQueuedLocks::Acquirer::ReleaseLocksOf
24
24
  # @param logger [::Logger,#debug]
25
25
  # - Logger object used from `configuration` layer (see config['logger']);
26
26
  # - See RedisQueuedLocks::Logging::VoidLogger for example;
27
- # @param isntrumenter [#notify]
27
+ # @param instrumenter [#notify]
28
28
  # See RedisQueuedLocks::Instrument::ActiveSupport for example.
29
29
  # @param instrument [NilClass,Any]
30
30
  # - Custom instrumentation data wich will be passed to the instrumenter's payload
@@ -4,6 +4,7 @@
4
4
  # @since 1.0.0
5
5
  module RedisQueuedLocks::Acquirer
6
6
  require_relative 'acquirer/acquire_lock'
7
+ require_relative 'acquirer/lock_series_poc'
7
8
  require_relative 'acquirer/release_lock'
8
9
  require_relative 'acquirer/release_all_locks'
9
10
  require_relative 'acquirer/release_locks_of'
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api public
4
4
  # @since 1.0.0
5
- # @version 1.14.0
5
+ # @version 1.16.0
6
6
  # rubocop:disable Metrics/ClassLength
7
7
  class RedisQueuedLocks::Client
8
8
  # @return [RedisClient]
@@ -301,11 +301,11 @@ class RedisQueuedLocks::Client
301
301
  # - marks the method that everything should be instrumneted
302
302
  # despite the enabled instrumentation sampling;
303
303
  # - makes sense when instrumentation sampling is enabled;
304
- # @param block [Block]
305
- # A block of code that should be executed after the successfully acquired lock.
304
+ # @yield A block of code that should be executed after the successfully acquired lock.
305
+ # @yieldreturn [Any]
306
306
  # @return [Hash<Symbol,Any>,yield]
307
307
  # - Format: { ok: true/false, result: Symbol/Hash }.
308
- # - If block is given the result of block's yeld will be returned.
308
+ # - If block is given the result of block's yield will be returned.
309
309
  #
310
310
  # @api public
311
311
  # @since 1.0.0
@@ -381,12 +381,156 @@ class RedisQueuedLocks::Client
381
381
  end
382
382
  # rubocop:enable Metrics/MethodLength
383
383
 
384
+ # NOTE: Lock Series PoC
385
+ # rubocop:disable all
386
+ # @api public
387
+ # @since 1.16.0
388
+ def lock_series( # steep:ignore
389
+ *lock_names,
390
+ detailed_result: false,
391
+ ttl: config['default_lock_ttl'], # steep:ignore
392
+ queue_ttl: config['default_queue_ttl'], # steep:ignore
393
+ timeout: config['try_to_lock_timeout'], # steep:ignore
394
+ timed: config['is_timed_by_default'], # steep:ignore
395
+ retry_count: config['retry_count'], # steep:ignore
396
+ retry_delay: config['retry_delay'], # steep:ignore
397
+ retry_jitter: config['retry_jitter'], # steep:ignore
398
+ raise_errors: false,
399
+ fail_fast: false,
400
+ conflict_strategy: config['default_conflict_strategy'], # steep:ignore
401
+ read_write_mode: :write,
402
+ access_strategy: config['default_access_strategy'], # steep:ignore
403
+ identity: uniq_identity,
404
+ meta: nil,
405
+ detailed_acq_timeout_error: config['detailed_acq_timeout_error'], # steep:ignore
406
+ logger: config['logger'], # steep:ignore
407
+ log_lock_try: config['log_lock_try'], # steep:ignore
408
+ instrumenter: config['instrumenter'], # steep:ignore
409
+ instrument: nil,
410
+ log_sampling_enabled: config['log_sampling_enabled'], # steep:ignore
411
+ log_sampling_percent: config['log_sampling_percent'], # steep:ignore
412
+ log_sampler: config['log_sampler'], # steep:ignore
413
+ log_sample_this: false,
414
+ instr_sampling_enabled: config['instr_sampling_enabled'], # steep:ignore
415
+ instr_sampling_percent: config['instr_sampling_percent'], # steep:ignore
416
+ instr_sampler: config['instr_sampler'], # steep:ignore
417
+ instr_sample_this: false,
418
+ &block
419
+ )
420
+ RedisQueuedLocks::Acquirer::LockSeriesPoC.lock_series_poc( # steep:ignore
421
+ redis_client,
422
+ lock_names,
423
+ detailed_result:,
424
+ process_id: RedisQueuedLocks::Resource.get_process_id,
425
+ thread_id: RedisQueuedLocks::Resource.get_thread_id,
426
+ fiber_id: RedisQueuedLocks::Resource.get_fiber_id,
427
+ ractor_id: RedisQueuedLocks::Resource.get_ractor_id,
428
+ ttl:,
429
+ queue_ttl:,
430
+ timeout:,
431
+ timed:,
432
+ retry_count:,
433
+ retry_delay:,
434
+ retry_jitter:,
435
+ raise_errors:,
436
+ instrumenter:,
437
+ identity:,
438
+ fail_fast:,
439
+ conflict_strategy:,
440
+ read_write_mode:,
441
+ access_strategy:,
442
+ meta:,
443
+ detailed_acq_timeout_error:,
444
+ logger:,
445
+ log_lock_try:,
446
+ instrument:,
447
+ log_sampling_enabled:,
448
+ log_sampling_percent:,
449
+ log_sampler:,
450
+ log_sample_this:,
451
+ instr_sampling_enabled:,
452
+ instr_sampling_percent:,
453
+ instr_sampler:,
454
+ instr_sample_this:,
455
+ &block
456
+ )
457
+ end
458
+ # rubocop:enable all
459
+
460
+ # NOTE: Lock Series PoC
461
+ # rubocop:disable all
462
+ # @api public
463
+ # @since 1.16.0
464
+ def lock_series!( # steep:ignore
465
+ *lock_names,
466
+ detailed_result: false,
467
+ ttl: config['default_lock_ttl'], # steep:ignore
468
+ queue_ttl: config['default_queue_ttl'], # steep:ignore
469
+ timeout: config['try_to_lock_timeout'], # steep:ignore
470
+ timed: config['is_timed_by_default'], # steep:ignore
471
+ retry_count: config['retry_count'], # steep:ignore
472
+ retry_delay: config['retry_delay'], # steep:ignore
473
+ retry_jitter: config['retry_jitter'], # steep:ignore
474
+ fail_fast: false,
475
+ conflict_strategy: config['default_conflict_strategy'], # steep:ignore
476
+ read_write_mode: :write,
477
+ access_strategy: config['default_access_strategy'], # steep:ignore
478
+ identity: uniq_identity,
479
+ meta: nil,
480
+ detailed_acq_timeout_error: config['detailed_acq_timeout_error'], # steep:ignore
481
+ logger: config['logger'], # steep:ignore
482
+ log_lock_try: config['log_lock_try'], # steep:ignore
483
+ instrumenter: config['instrumenter'], # steep:ignore
484
+ instrument: nil,
485
+ log_sampling_enabled: config['log_sampling_enabled'], # steep:ignore
486
+ log_sampling_percent: config['log_sampling_percent'], # steep:ignore
487
+ log_sampler: config['log_sampler'], # steep:ignore
488
+ log_sample_this: false,
489
+ instr_sampling_enabled: config['instr_sampling_enabled'], # steep:ignore
490
+ instr_sampling_percent: config['instr_sampling_percent'], # steep:ignore
491
+ instr_sampler: config['instr_sampler'], # steep:ignore
492
+ instr_sample_this: false,
493
+ &block
494
+ )
495
+ lock_series( # steep:ignore
496
+ *lock_names, # steep:ignore
497
+ detailed_result:,
498
+ ttl:,
499
+ queue_ttl:,
500
+ timeout:,
501
+ timed:,
502
+ retry_count:,
503
+ retry_delay:,
504
+ retry_jitter:,
505
+ raise_errors: true,
506
+ fail_fast:,
507
+ conflict_strategy:,
508
+ read_write_mode:,
509
+ access_strategy:,
510
+ identity:,
511
+ meta:,
512
+ detailed_acq_timeout_error:,
513
+ logger:,
514
+ log_lock_try:,
515
+ instrumenter:,
516
+ instrument:,
517
+ log_sampling_enabled:,
518
+ log_sampling_percent:,
519
+ log_sampler:,
520
+ log_sample_this:,
521
+ instr_sampling_enabled:,
522
+ instr_sampling_percent:,
523
+ instr_sampler:,
524
+ instr_sample_this:,
525
+ &block
526
+ )
527
+ end
528
+
384
529
  # @note See #lock method signature.
385
530
  #
386
531
  # @api public
387
532
  # @since 1.0.0
388
533
  # @version 1.13.0
389
- # rubocop:disable Metrics/MethodLength
390
534
  def lock!(
391
535
  lock_name,
392
536
  ttl: config['default_lock_ttl'], # steep:ignore
@@ -16,7 +16,7 @@ module RedisQueuedLocks::Config::DSL
16
16
  #
17
17
  # @api private
18
18
  # @since 1.13.0
19
- def config_setters()= @config_setters # rubocop:disable Style/DefWithParentheses
19
+ def config_setters()= @config_setters
20
20
 
21
21
  # NOTE:
22
22
  # 1. Style/DefWithParentheses rubocop's cop incorrectly drops `()` from method definition
@@ -28,7 +28,7 @@ module RedisQueuedLocks::Config::DSL
28
28
  #
29
29
  # @api private
30
30
  # @since 1.13.0
31
- def config_validators()= @config_validators # rubocop:disable Style/DefWithParentheses
31
+ def config_validators()= @config_validators
32
32
 
33
33
  # @param config_key [String]
34
34
  # @param validator [Block]
@@ -5,7 +5,8 @@
5
5
  module RedisQueuedLocks::Logging::VoidLogger
6
6
  class << self
7
7
  # @param progname [Any]
8
- # @parma block [Block]
8
+ # @yield
9
+ # @yieldreturn [Any]
9
10
  # @return [void]
10
11
  #
11
12
  # @api public
@@ -13,7 +14,8 @@ module RedisQueuedLocks::Logging::VoidLogger
13
14
  def warn(progname = nil, &block); end
14
15
 
15
16
  # @param progname [Any]
16
- # @parma block [Block]
17
+ # @yield
18
+ # @yieldreturn [Any]
17
19
  # @return [void]
18
20
  #
19
21
  # @api public
@@ -21,7 +23,8 @@ module RedisQueuedLocks::Logging::VoidLogger
21
23
  def unknown(progname = nil, &block); end
22
24
 
23
25
  # @param progname [Any]
24
- # @parma block [Block]
26
+ # @yield
27
+ # @yieldreturn [Any]
25
28
  # @return [void]
26
29
  #
27
30
  # @api public
@@ -29,7 +32,8 @@ module RedisQueuedLocks::Logging::VoidLogger
29
32
  def log(progname = nil, &block); end
30
33
 
31
34
  # @param progname [Any]
32
- # @parma block [Block]
35
+ # @yield
36
+ # @yieldreturn [Any]
33
37
  # @return [void]
34
38
  #
35
39
  # @api public
@@ -37,7 +41,8 @@ module RedisQueuedLocks::Logging::VoidLogger
37
41
  def info(progname = nil, &block); end
38
42
 
39
43
  # @param progname [Any]
40
- # @parma block [Block]
44
+ # @yield
45
+ # @yieldreturn [Any]
41
46
  # @return [void]
42
47
  #
43
48
  # @api public
@@ -45,7 +50,8 @@ module RedisQueuedLocks::Logging::VoidLogger
45
50
  def error(progname = nil, &block); end
46
51
 
47
52
  # @param progname [Any]
48
- # @parma block [Block]
53
+ # @yield
54
+ # @yieldreturn [Any]
49
55
  # @return [void]
50
56
  #
51
57
  # @api public
@@ -53,7 +59,8 @@ module RedisQueuedLocks::Logging::VoidLogger
53
59
  def fatal(progname = nil, &block); end
54
60
 
55
61
  # @param progname [Any]
56
- # @parma block [Block]
62
+ # @yield
63
+ # @yieldreturn [Any]
57
64
  # @return [void]
58
65
  #
59
66
  # @api public
@@ -63,7 +70,8 @@ module RedisQueuedLocks::Logging::VoidLogger
63
70
  # @param severity [Any]
64
71
  # @param message [Any]
65
72
  # @param progname [Any]
66
- # @param block [Block]
73
+ # @yield
74
+ # @yieldreturn [Any]
67
75
  # @return [void]
68
76
  #
69
77
  # @api public
@@ -71,7 +79,7 @@ module RedisQueuedLocks::Logging::VoidLogger
71
79
  def add(severity = nil, message = nil, progname = nil, &block); end
72
80
 
73
81
  # @param message [Any]
74
- # @retorun [void]
82
+ # @return [void]
75
83
  #
76
84
  # @api public
77
85
  # @since 1.0.0
@@ -75,10 +75,8 @@ module RedisQueuedLocks::Logging
75
75
  # - see https://github.com/reidmorrison/semantic_logger
76
76
  # - convinient/conventional way to support the popular `broadcast` RubyOnRails's logger;
77
77
  # - see https://api.rubyonrails.org/classes/ActiveSupport/BroadcastLogger.html
78
- # rubocop:disable Layout/LineLength
79
78
  return true if defined?(::SemanticLogger::Logger) && logger.is_a?(::SemanticLogger::Logger)
80
79
  return true if defined?(::ActiveSupport::BroadcastLogger) && logger.is_a?(::ActiveSupport::BroadcastLogger)
81
- # rubocop:enable Layout/LineLength
82
80
 
83
81
  # NOTE: should provide `#debug` method.
84
82
  return false unless logger.respond_to?(:debug)
@@ -152,7 +152,7 @@ module RedisQueuedLocks::Resource
152
152
  # @param acquirer_position [Float]
153
153
  # A time (epoch, seconds.milliseconds) that represents
154
154
  # the acquirer position in lock request queue.
155
- # @parma queue_ttl [Integer]
155
+ # @param queue_ttl [Integer]
156
156
  # In second.
157
157
  # @return [Boolean]
158
158
  # Is the lock request time limit has reached or not.
@@ -5,7 +5,7 @@
5
5
  class RedisQueuedLocks::Swarm::FlushZombies < RedisQueuedLocks::Swarm::SwarmElement::Isolated
6
6
  class << self
7
7
  # @param redis_client [RedisClient]
8
- # @parma zombie_ttl [Integer]
8
+ # @param zombie_ttl [Integer]
9
9
  # @param lock_scan_size [Integer]
10
10
  # @param queue_scan_size [Integer]
11
11
  # @return [Hash<Symbol,Boolean|Set<String>]] Format:
@@ -154,7 +154,7 @@ class RedisQueuedLocks::Swarm::SwarmElement::Isolated
154
154
  #
155
155
  # @api private
156
156
  # @since 1.9.0
157
- # rubocop:disable Layout/ClassStructure, Lint/IneffectiveAccessModifier
157
+ # rubocop:disable Layout/ClassStructure, Lint/IneffectiveAccessModifier, Metrics/MethodLength
158
158
  def self.swarm_loop(&main_loop_spawner)
159
159
  # NOTE:
160
160
  # This self.-related part of code is placed in the middle of class in order
@@ -177,7 +177,10 @@ class RedisQueuedLocks::Swarm::SwarmElement::Isolated
177
177
  # @type var main_loop: Thread
178
178
  RedisQueuedLocks::Utilities.thread_state(main_loop)
179
179
  end
180
- Ractor.yield({ main_loop: { alive: main_loop_alive, state: main_loop_state } })
180
+ # NOTE: (will be reworked with Ractor::Port in the next RQL release (~1.17))
181
+ Ractor.yield({ # steep:ignore
182
+ main_loop: { alive: main_loop_alive, state: main_loop_state }
183
+ })
181
184
  when :is_active
182
185
  Ractor.yield(main_loop != nil && main_loop.alive?) # steep:ignore
183
186
  when :start
@@ -191,7 +194,7 @@ class RedisQueuedLocks::Swarm::SwarmElement::Isolated
191
194
  end
192
195
  end
193
196
  end
194
- # rubocop:enable Layout/ClassStructure, Lint/IneffectiveAccessModifier
197
+ # rubocop:enable Layout/ClassStructure, Lint/IneffectiveAccessModifier, Metrics/MethodLength
195
198
 
196
199
  # @return [Boolean]
197
200
  #
@@ -12,7 +12,8 @@ class RedisQueuedLocks::Utilities::Lock
12
12
  @lock = ::Monitor.new
13
13
  end
14
14
 
15
- # @param block [Block]
15
+ # @yield
16
+ # @yieldreturn [Any]
16
17
  # @return [Any]
17
18
  #
18
19
  # @api private
@@ -36,7 +36,14 @@ module RedisQueuedLocks::Utilities
36
36
  # @since 1.9.0
37
37
  RACTOR_STATUS_PATTERN = /\A.*?\s(?<status>\w+)>\z/i
38
38
 
39
- # @param block [Block]
39
+ # @return [Boolean]
40
+ #
41
+ # @api public
42
+ # @since ?.?.?
43
+ RUBY_OVER_4 = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('4.0.0')
44
+
45
+ # @yield
46
+ # @yieldreturn [Any]
40
47
  # @return [Any]
41
48
  #
42
49
  # @api private
@@ -86,7 +93,7 @@ module RedisQueuedLocks::Utilities
86
93
  # - "failed" (thread is terminated with an exception);
87
94
  # See Thread#status official documentation.
88
95
  #
89
- # @param [Thread]
96
+ # @param thread [Thread]
90
97
  # @return [String]
91
98
  #
92
99
  # @api private
@@ -97,4 +104,12 @@ module RedisQueuedLocks::Utilities
97
104
  return 'failed' if status == nil
98
105
  status #: String
99
106
  end
107
+
108
+ # @return [Boolean]
109
+ #
110
+ # @api public
111
+ # @since 1.16.0
112
+ def ruby_over_4?
113
+ RUBY_OVER_4
114
+ end
100
115
  end
@@ -5,6 +5,6 @@ module RedisQueuedLocks
5
5
  #
6
6
  # @api public
7
7
  # @since 0.0.1
8
- # @version 1.15.1
9
- VERSION = '1.15.1'
8
+ # @version 1.16.0
9
+ VERSION = '1.16.0'
10
10
  end
@@ -2,11 +2,13 @@ module RedisQueuedLocks
2
2
  module Utilities
3
3
  RACTOR_LIVENESS_PATTERN: Regexp
4
4
  RACTOR_STATUS_PATTERN: Regexp
5
+ RUBY_OVER_4: bool
5
6
 
6
7
  def self?.run_non_critical: () { (?) -> untyped } -> untyped?
7
8
  def self?.ractor_status: (Ractor ractor) -> String
8
9
  def self?.ractor_alive?: (Ractor ractor) -> bool
9
10
  def self?.thread_state: (Thread thread) -> String
10
11
  def self?.clock_gettime: () -> Integer
12
+ def self?.ruby_over_4?: () -> bool
11
13
  end
12
14
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis_queued_locks
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.15.1
4
+ version: 1.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov
@@ -65,6 +65,7 @@ files:
65
65
  - lib/redis_queued_locks/acquirer/is_queued.rb
66
66
  - lib/redis_queued_locks/acquirer/keys.rb
67
67
  - lib/redis_queued_locks/acquirer/lock_info.rb
68
+ - lib/redis_queued_locks/acquirer/lock_series_poc.rb
68
69
  - lib/redis_queued_locks/acquirer/locks.rb
69
70
  - lib/redis_queued_locks/acquirer/queue_info.rb
70
71
  - lib/redis_queued_locks/acquirer/queues.rb