redis_queued_locks 1.15.1 → 1.16.1

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: 0bd0b3bb446d6ce5af47949de25834b0c4f87a3fcb1090bf9604fba129354468
4
+ data.tar.gz: b9c8c49560e193db92d63fc232c31fbdf1ea03b72b28fc778c3fcc22da3af1d1
5
5
  SHA512:
6
- metadata.gz: 6b6066096f58daeafab9c6c06f815628be1d2b32a56374f1fc3d1d6cdcff4f7aba6271d20efc16c0439084e629b6867e66eeb58d0091454b5441e29310fcb83b
7
- data.tar.gz: 43751ba0011d662474c47ad014620d2b43d4555f04ae41f1a23ac1f7072aa20736a430e5a7db2a494449fe3245331e48bcf8453b91ba56c4ceba08dafaf5ed42
6
+ metadata.gz: dcea0a7ee67e7a727fe0d92d88bf8f6b0a946c279fce3d43f34e6fe05844cf156e9679d91fcd12f450ebd9029a0aabd9f61b9c5224b7a88fa4551202bd716939
7
+ data.tar.gz: 0c0b132e65106ce28f68a41278adc7981150b3a50d73952f55bfa22d0b735e8713924a20adca4caedd0fb744587a5675cbab0a78339c02d695b516129a2bd480
data/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.16.1] - 2026-01-28
4
+ ### Changed
5
+ - `lock_series` PoC:
6
+ - added own instrumentation visitor and log ivsitor (`RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor` and
7
+ `RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor` respectively) and all `lock_series`-related
8
+ instrumentation/log methods were moved to these modules;
9
+ - documentaiton updates;
10
+ - YARDOC documentaiton updates;
11
+ ### Added
12
+ - `lock_series` PoC updates:
13
+ - added missing instrumentation event `lock_series_obtained`;
14
+ - added more data to the `detailed_event: true` lock invocation result:
15
+ - `locks_release_strategy` - `:immediate_release_after_yield` or `:redis_key_ttl`
16
+ ### Fixed
17
+ - `lock_series` PoC:
18
+ - incorrect instrumentation/logs data:
19
+ - `expire_lock_series` log and instrumentation event should not be produced
20
+ when locks were obtained without a block of code (for this case obtained locks
21
+ lives in redis with their TTL);
22
+ - `locks_released_at` and `locks_hold_time` of the detailed invocation result (`detailed: true`)
23
+ has nil values in case when locks are obtained without a block of code
24
+ (lokcs are not released at this moment and lives in redis with their TTL);
25
+
26
+ ## [1.16.0] - 2026-01-27
27
+ ### Added
28
+ - Brand new Feature **"Lock series of locks"**
29
+ - (*Proof of Concpet*-based implementation at the moment)
30
+ - lock series of lock simultainously (and release them all at the finish of your the invocation of block of code under the obtained locks);
31
+ - exmaple: `client.lock_series("a", "b", "c")` (or in exceptional-based way `client.lock_series!("a", "b", "c")`;
32
+ ### Changed
33
+ - YARDOC documentation updates in order to prearet to the 100% yardoc-coverage;
34
+ - updated dev deps;
35
+ ### Fixed
36
+ - fixed some yardoc documentation typos;
37
+
3
38
  ## [1.15.1] - 2025-10-22
4
39
  ### Fixed
5
40
  - **Config Layer**:
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,120 @@ 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
+ If it is not possible to obtain all locks at the same time (taking into queue ttl, retry attempts count for each lock, etc)
863
+ - no any lock will be acquired.
864
+
865
+ Method options is the same as for `lock` method: `retry_count`, `retry_jitter`, `ttl`, `queue_ttl`, etc. Each option will be
866
+ applied with the same value to the each lock acquirement process (for each lock in a series separately).
867
+
868
+ **IMPORTANT DETAILS AND LIMITATIONS**:
869
+ - (this behavior will be reworked with another algorithm in future);
870
+ - each lock will be released with the next formula:
871
+ - `lock position with reverse numbering` * `ttl`
872
+ - (positions exmaple: ["a", "b", "c"] => "a" position is 3, "b" position is 2, "c" position is 1);
873
+ - this means that in case of 3 locks (`lock_series("x", "y", "z", ttl: 5_000`) with `5 seconds TTL` your locks will be with the next TTL:
874
+ - `"x"` - 15 seconds
875
+ - `"y"` - 10 seconds
876
+ - `"z"` - 5 seconds
877
+ - when your block of code has completed their execution - all locks will be released immediatly;
878
+ - if you did not pass a block - all locks will be released with their forumla-based TTL described above;
879
+ - **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`);
880
+
881
+ ##### How to use:
882
+
883
+ - method signature is the same as the `lock` method;
884
+
885
+ ```ruby
886
+ # typical logic-oriented way
887
+ client.lock_series("a", "b", "c") { ...some_code... }
888
+
889
+ # result-oriented way:
890
+ # - (block is passed)
891
+ detailed_result = client.lock_series('x', 'y', 'z', detailed_result: true) { 1 + 2 }
892
+ detailed_result # =>
893
+ {
894
+ yield_result: 3, # result of your block of code
895
+ lock_release_strategy: :immediate_release_after_yield, # (block is passed)
896
+ locks_released_at: 1769506100.676884, # (instrumentation) release time, epoch
897
+ locks_acq_time: 7.0, # (instrumentation) time spent to acquire all locks, milliseconds
898
+ locks_hold_time: 0.65, # (instrumentation) lock series hold period (hold preiod of all required locks), milliseconds
899
+ lock_series: ["x", "y", "z"], # your locks
900
+ rql_lock_series: ["rql:lock:x", "rql:lock:y", "rql:lock:z"] # internal RQL lock-keys of your locks
901
+ }
902
+
903
+ # - (block is not passed)
904
+ detailed_result = client.lock_series('x', 'y', 'z', detailed_result: true)
905
+ detailed_result # =>
906
+ {
907
+ yield_result: nil, # block is not given - nothing to return
908
+ lock_release_strategy: :redis_key_ttl, # block is not given - locks are living in redis with their TTL
909
+ locks_released_at: nil, # (instrumentation) # block is not given - locks are living in redis with their TTL
910
+ locks_acq_time: 7.0, # (instrumentation) time spent to acquire all locks, milliseconds
911
+ locks_hold_time: nil, # (instrumentation) # block is not given - locks are living in redis with their TTL
912
+ lock_series: ["x", "y", "z"], # your locks
913
+ rql_lock_series: ["rql:lock:x", "rql:lock:y", "rql:lock:z"] # internal RQL lock-keys of your locks
914
+ }
915
+ ```
916
+
917
+ ```ruby
918
+ # (failed lock obtainig) when lock series can not be obtained for some reasons
919
+ # for example: one of required lock in series is still obtained and can not be obtained now (and all retries are exhausted)
920
+
921
+ # - lock 'a' is obtained with long ttl
922
+ client.lock('a', ttl: 10_000_000)
923
+
924
+ # - try to bobtain series of locks with `a` inside the series (`a` can not be obtained for now)
925
+ client.lock_seires('c', 'a', 'b') # ... `a` is still obtained
926
+ # =>
927
+ {
928
+ ok: false,
929
+ result: {
930
+ error: :failed_to_acquire_lock_series,
931
+ detailed_errors: { 'a' => { ok: false, result: :retry_limit_reached } },
932
+ lock_series: ["c", "a", "b"],
933
+ acquired_locks: ["c"],
934
+ missing_locks: ["a", "b"],
935
+ failed_on_lock: ["a"],
936
+ }
937
+ }
938
+ ```
939
+
940
+ ##### New instrumentaiton events and logs:
941
+
942
+ ```ruby
943
+ # NEW instrumentation events (examples)
944
+ "redis_queued_locks.lock_series_obtained" => # {lock_keys: ["rql:lock:s", "rql:lock:t", "rql:lock:u"], ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: "2026-01-28 22:05:06 +0300", acq_time: 2.61, instrument: nil}
945
+ "redis_queued_locks.lock_series_hold_and_release" => # {lock_keys: ["rql:lock:x", "rql:lock:y", "rql:lock:z"], hold_time: 0.29, ttl: 5000, acq_id: "rql:acq:4486/1696/1704/1712/e6fd0da7991e4303", hst_id: "rql:hst:4486/1696/1712/e6fd0da7991e4303", ts: 1769627106.4717052, acq_time: 4.26, instrument: nil}
946
+ ```
947
+
948
+ ```shell
949
+ # NEW logs (examples)
950
+ [redis_queued_locks.start_lock_series_obtaining] lock_keys => '["rql:lock:a", "rql:lock:b"]'queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued'
951
+ [redis_queued_locks.lock_series_obtained] lock_keys => '["rql:lock:a", "rql:lock:b"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued' acq_time => 2.55 (ms)
952
+ [redis_queued_locks.expire_lock_series] lock_keys => '["rql:lock:x", "rql:lock:y", "rql:lock:z"]' queue_ttl => 15 acq_id => 'rql:acq:4486/1696/1704/1712/e6fd0da7991e4303' hst_id => 'rql:hst:4486/1696/1712/e6fd0da7991e4303' acs_strat => 'queued'
953
+ ```
954
+
955
+ ----
956
+
957
+ #### #lock_series! - (PoC) exceptional `lock_series`
958
+
959
+ > (IMPORTANT!) "Proof of Concept" realization. Will be reworked in the future (very-very soon)
960
+
961
+ For more details see `lock_series` [docs](#lock_series---poc-acquire-a-series-of-locks)
962
+
963
+ ```ruby
964
+ client.lock_series!("x", "y", "z") { ...some_code... }
965
+ ```
966
+
967
+ ----
968
+
853
969
  #### #lock_info
854
970
 
855
971
  <sup>\[[back to top](#usage)\]</sup>
@@ -2328,6 +2444,8 @@ Detalized event semantics and payload structure:
2328
2444
  - `write` - waits - `write`;
2329
2445
  - **write** mode is a default behavior for all RQL locks;
2330
2446
  - **Minor**:
2447
+ - an ability to return all insturmentation metrics from the `lock` invocation (and after block `yield`ing);
2448
+ - think about "thread priority" configuration :thinking:;
2331
2449
  - add `hst_id` to all methods that works with queues info;
2332
2450
  - 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
2451
  - 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 +2476,8 @@ Detalized event semantics and payload structure:
2358
2476
  - **Research**: support for `Valkey` database backend (https://github.com/valkey-io/valkey) (https://valkey.io/);
2359
2477
  - **Research**: support for `Garnet` database backend (https://microsoft.github.io/) (https://github.com/microsoft/garnet);
2360
2478
  - 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);
2479
+ - yardoc docs with CI check (full doc coverage check);
2480
+
2361
2481
  ---
2362
2482
 
2363
2483
  ## Build and Develop
@@ -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]
@@ -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,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # NOTE: Lock Series PoC
4
+ # steep:ignore
5
+ # @api private
6
+ # @since 1.16.1
7
+ module RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor # steep:ignore
8
+ class << self
9
+ # NOTE: Lock Series PoC
10
+ # @api private
11
+ # @since 1.16.1
12
+ def lock_series_obtained( # steep:ignore
13
+ instrumenter,
14
+ instr_sampled,
15
+ lock_keys,
16
+ ttl,
17
+ acq_id,
18
+ hst_id,
19
+ ts,
20
+ acq_time,
21
+ instrument
22
+ )
23
+ return unless instr_sampled
24
+ instrumenter.notify('redis_queued_locks.lock_series_obtained', {
25
+ lock_keys:, ttl:, acq_id:, hst_id:, ts:, acq_time:, instrument:
26
+ }) rescue nil
27
+ end
28
+
29
+ # NOTE: Lock Series PoC
30
+ # @api private
31
+ # @since 1.16.1
32
+ def lock_series_hold_and_release( # steep:ignore
33
+ instrumenter,
34
+ instr_sampled,
35
+ lock_keys,
36
+ ttl,
37
+ acq_id,
38
+ hst_id,
39
+ ts,
40
+ acq_time,
41
+ hold_time,
42
+ instrument
43
+ )
44
+ return unless instr_sampled
45
+ instrumenter.notify('redis_queued_locks.lock_series_hold_and_release', {
46
+ lock_keys:, hold_time:, ttl:, acq_id:, hst_id:, ts:, acq_time:, instrument:
47
+ }) # rescue nil
48
+ end
49
+ end
50
+ end
51
+ # steep:ignore
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ # NOTE: Lock Series PoC
4
+ # steep:ignore
5
+ # @api private
6
+ # @since 1.16.1
7
+ module RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor # steep:ignore
8
+ class << self
9
+ # NOTE: Lock Series PoC
10
+ # @api private
11
+ # @since 1.16.0
12
+ def start_lock_series_obtaining( # steep:ignore
13
+ logger,
14
+ log_sampled,
15
+ lock_keys,
16
+ queue_ttl,
17
+ acquirer_id,
18
+ host_id,
19
+ access_strategy
20
+ )
21
+ return unless log_sampled
22
+
23
+ logger.debug do
24
+ "[redis_queued_locks.start_lock_series_obtaining] " \
25
+ "lock_keys => '#{lock_keys.inspect}'" \
26
+ "queue_ttl => #{queue_ttl} " \
27
+ "acq_id => '#{acquirer_id}' " \
28
+ "hst_id => '#{host_id}' " \
29
+ "acs_strat => '#{access_strategy}'"
30
+ end # rescue nil
31
+ end
32
+
33
+ # NOTE: Lock Series PoC
34
+ # @api private
35
+ # @since 1.16.0
36
+ def lock_series_obtained( # steep:ignore
37
+ logger,
38
+ log_sampled,
39
+ lock_keys,
40
+ queue_ttl,
41
+ acquirer_id,
42
+ host_id,
43
+ acq_time,
44
+ access_strategy
45
+ )
46
+ return unless log_sampled
47
+
48
+ logger.debug do
49
+ "[redis_queued_locks.lock_series_obtained] " \
50
+ "lock_keys => '#{lock_keys.inspect}' " \
51
+ "queue_ttl => #{queue_ttl} " \
52
+ "acq_id => '#{acquirer_id}' " \
53
+ "hst_id => '#{host_id}' " \
54
+ "acs_strat => '#{access_strategy}' " \
55
+ "acq_time => #{acq_time} (ms)"
56
+ end # rescue nil
57
+ end
58
+
59
+ # NOTE: Lock Series PoC
60
+ # @api private
61
+ # @since 1.16.0
62
+ def expire_lock_series( # steep:ignore
63
+ logger,
64
+ log_sampled,
65
+ lock_series,
66
+ queue_ttl,
67
+ acquirer_id,
68
+ host_id,
69
+ access_strategy
70
+ )
71
+ return unless log_sampled
72
+
73
+ logger.debug do
74
+ "[redis_queued_locks.expire_lock_series] " \
75
+ "lock_keys => '#{lock_series.inspect}' " \
76
+ "queue_ttl => #{queue_ttl} " \
77
+ "acq_id => '#{acquirer_id}' " \
78
+ "hst_id => '#{host_id}' " \
79
+ "acs_strat => '#{access_strategy}'"
80
+ end rescue nil
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,317 @@
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
+ require_relative 'lock_series_poc/log_visitor'
10
+ require_relative 'lock_series_poc/instr_visitor'
11
+
12
+ # @sine 1.16.0
13
+ extend RedisQueuedLocks::Acquirer::AcquireLock::YieldExpire
14
+
15
+ class << self
16
+ # @api private
17
+ # @since 1.16.0
18
+ # @version 1.16.1
19
+ def lock_series_poc( # steep:ignore
20
+ redis,
21
+ lock_names,
22
+ detailed_result:,
23
+ process_id:,
24
+ thread_id:,
25
+ fiber_id:,
26
+ ractor_id:,
27
+ ttl:,
28
+ queue_ttl:,
29
+ timeout:,
30
+ timed:,
31
+ retry_count:,
32
+ retry_delay:,
33
+ retry_jitter:,
34
+ raise_errors:,
35
+ instrumenter:,
36
+ identity:,
37
+ fail_fast:,
38
+ meta:,
39
+ detailed_acq_timeout_error:,
40
+ instrument:,
41
+ logger:,
42
+ log_lock_try:,
43
+ conflict_strategy:,
44
+ read_write_mode:,
45
+ access_strategy:,
46
+ log_sampling_enabled:,
47
+ log_sampling_percent:,
48
+ log_sampler:,
49
+ log_sample_this:,
50
+ instr_sampling_enabled:,
51
+ instr_sampling_percent:,
52
+ instr_sampler:,
53
+ instr_sample_this:,
54
+ &block
55
+ )
56
+ case meta
57
+ when Hash, NilClass then nil
58
+ else
59
+ raise(
60
+ RedisQueuedLocks::ArgumentError,
61
+ "`:meta` argument should be a type of NilClass or Hash, got #{meta.class}."
62
+ )
63
+ end
64
+
65
+ if meta.is_a?(::Hash) && (meta.any? do |key, _value|
66
+ key == 'acq_id' ||
67
+ key == 'hst_id' ||
68
+ key == 'ts' ||
69
+ key == 'ini_ttl' ||
70
+ key == 'lock_key' ||
71
+ key == 'rem_ttl' ||
72
+ key == 'spc_ext_ttl' ||
73
+ key == 'spc_cnt' ||
74
+ key == 'l_spc_ext_ini_ttl' ||
75
+ key == 'l_spc_ext_ts' ||
76
+ key == 'l_spc_ts'
77
+ end)
78
+ raise(
79
+ RedisQueuedLocks::ArgumentError,
80
+ '`:meta` keys can not overlap reserved lock data keys ' \
81
+ '"acq_id", "hst_id", "ts", "ini_ttl", "lock_key", "rem_ttl", "spc_cnt", ' \
82
+ '"spc_ext_ttl", "l_spc_ext_ini_ttl", "l_spc_ext_ts", "l_spc_ts"'
83
+ )
84
+ end
85
+
86
+ locks_count = lock_names.size
87
+
88
+ lock_acquirement_operations = lock_names.each_with_index.map do |lock_name, position|
89
+ lock_ttl =
90
+ ttl * (locks_count - position) +
91
+ retry_delay * (locks_count - position) +
92
+ retry_jitter * (locks_count - position)
93
+
94
+ { lock_name: lock_name, ttl: lock_ttl }
95
+ end
96
+
97
+ log_sampled = RedisQueuedLocks::Logging.should_log?(
98
+ log_sampling_enabled,
99
+ log_sample_this,
100
+ log_sampling_percent,
101
+ log_sampler
102
+ )
103
+
104
+ instr_sampled = RedisQueuedLocks::Instrument.should_instrument?(
105
+ instr_sampling_enabled,
106
+ instr_sample_this,
107
+ instr_sampling_percent,
108
+ instr_sampler
109
+ )
110
+
111
+ lock_keys_for_instrumentation = lock_names.map do |lock_name|
112
+ RedisQueuedLocks::Resource.prepare_lock_key(lock_name)
113
+ end
114
+
115
+ acquirer_id_for_instrumentation = RedisQueuedLocks::Resource.acquirer_identifier(
116
+ process_id,
117
+ thread_id,
118
+ fiber_id,
119
+ ractor_id,
120
+ identity
121
+ )
122
+
123
+ host_id_for_instrumentation = RedisQueuedLocks::Resource.host_identifier(
124
+ process_id,
125
+ thread_id,
126
+ ractor_id,
127
+ identity
128
+ )
129
+
130
+ RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.start_lock_series_obtaining( # steep:ignore
131
+ logger, log_sampled, lock_keys_for_instrumentation,
132
+ queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy
133
+ )
134
+
135
+ acq_start_time = RedisQueuedLocks::Utilities.clock_gettime
136
+ successfully_acquired_locks = [] # steep:ignore
137
+ failed_locks_and_errors = {} # steep:ignore
138
+ failed_on_lock = nil
139
+
140
+ lock_acquirement_operations.map do |lock_operation_options|
141
+ result =
142
+ begin
143
+ RedisQueuedLocks::Acquirer::AcquireLock.acquire_lock(
144
+ redis,
145
+ lock_operation_options[:lock_name],
146
+ process_id:,
147
+ thread_id:,
148
+ fiber_id:,
149
+ ractor_id:,
150
+ ttl: lock_operation_options[:ttl],
151
+ queue_ttl:,
152
+ timeout:,
153
+ timed:,
154
+ retry_count:,
155
+ retry_delay:,
156
+ retry_jitter:,
157
+ raise_errors:,
158
+ instrumenter:,
159
+ identity:,
160
+ fail_fast:,
161
+ meta:,
162
+ detailed_acq_timeout_error:,
163
+ instrument:,
164
+ logger:,
165
+ log_lock_try:,
166
+ conflict_strategy:,
167
+ read_write_mode:,
168
+ access_strategy:,
169
+ log_sampling_enabled:,
170
+ log_sampling_percent:,
171
+ log_sampler:,
172
+ log_sample_this:,
173
+ instr_sampling_enabled:,
174
+ instr_sampling_percent:,
175
+ instr_sampler:,
176
+ instr_sample_this:
177
+ )
178
+ rescue => error
179
+ if raise_errors && successfully_acquired_locks.any?
180
+ # NOTE: release all previously acquired locks if any next lock is already locked
181
+ successfully_acquired_locks.each do |operation_result|
182
+ lock_key = RedisQueuedLocks::Resource.prepare_lock_key(operation_result[:lock_name])
183
+ redis.with do |conn|
184
+ conn.multi(watch: [lock_key]) do |transact|
185
+ transact.call('DEL', lock_key)
186
+ end
187
+ end
188
+ end
189
+ end
190
+ raise(error)
191
+ end
192
+
193
+ if result[:ok]
194
+ successfully_acquired_locks << {
195
+ lock_name: lock_operation_options[:lock_name],
196
+ ok: result[:ok],
197
+ result: result[:result]
198
+ }
199
+ else
200
+ failed_on_lock = lock_operation_options[:lock_name]
201
+ failed_locks_and_errors[lock_operation_options[:lock_name]] = result
202
+ break
203
+ end
204
+ end
205
+
206
+ if (successfully_acquired_locks.size == lock_names.size && (successfully_acquired_locks.all? { |res| res[:ok] }))
207
+ acq_end_time = RedisQueuedLocks::Utilities.clock_gettime
208
+ acq_time = ((acq_end_time - acq_start_time) / 1_000.0).ceil(2)
209
+ ts = Time.now.to_s
210
+
211
+ RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.lock_series_obtained( # steep:ignore
212
+ logger, log_sampled, lock_keys_for_instrumentation,
213
+ queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation,
214
+ acq_time, access_strategy
215
+ )
216
+
217
+ RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor.lock_series_obtained( # steep:ignore
218
+ instrumenter, instr_sampled, lock_keys_for_instrumentation,
219
+ ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, ts, acq_time, instrument
220
+ )
221
+
222
+ yield_time = RedisQueuedLocks::Utilities.clock_gettime
223
+ ttl_shift = (
224
+ (yield_time - acq_end_time) / 1_000.0 -
225
+ RedisQueuedLocks::Resource::REDIS_TIMESHIFT_ERROR
226
+ ).ceil(2)
227
+
228
+ yield_result = yield_expire( # steep:ignore
229
+ redis,
230
+ logger,
231
+ lock_keys_for_instrumentation.last,
232
+ acquirer_id_for_instrumentation,
233
+ host_id_for_instrumentation,
234
+ access_strategy,
235
+ timed,
236
+ ttl_shift,
237
+ ttl,
238
+ queue_ttl,
239
+ meta,
240
+ log_sampled,
241
+ instr_sampled,
242
+ false, # should_expire (expire manually)
243
+ false, # should_decrease (expire manually)
244
+ &block
245
+ )
246
+
247
+ is_lock_manually_released = false
248
+
249
+ # expire locks manually
250
+ if block_given?
251
+ redis.with do |conn|
252
+ # use transaction in order to exclude any cross-locking during the group expiration
253
+ conn.multi(watch: lock_keys_for_instrumentation) do |transaction|
254
+ lock_keys_for_instrumentation.each do |lock_key|
255
+ transaction.call('EXPIRE', lock_key, '0')
256
+ end
257
+ end
258
+ end
259
+ is_lock_manually_released = true
260
+ end
261
+
262
+ rel_time = RedisQueuedLocks::Utilities.clock_gettime
263
+ hold_time = ((rel_time - acq_end_time) / 1_000.0).ceil(2)
264
+ ts = Time.now.to_f
265
+
266
+ RedisQueuedLocks::Acquirer::LockSeriesPoC::LogVisitor.expire_lock_series( # steep:ignore
267
+ logger, log_sampled, lock_keys_for_instrumentation,
268
+ queue_ttl, acquirer_id_for_instrumentation, host_id_for_instrumentation, access_strategy
269
+ ) if is_lock_manually_released
270
+
271
+ RedisQueuedLocks::Acquirer::LockSeriesPoC::InstrVisitor.lock_series_hold_and_release( # steep:ignore
272
+ instrumenter,
273
+ instr_sampled,
274
+ lock_keys_for_instrumentation,
275
+ ttl,
276
+ acquirer_id_for_instrumentation,
277
+ host_id_for_instrumentation,
278
+ ts,
279
+ acq_time,
280
+ hold_time,
281
+ instrument
282
+ ) if is_lock_manually_released
283
+
284
+ if detailed_result
285
+ {
286
+ yield_result: yield_result,
287
+ locks_release_strategy: block_given? ? :immediate_release_after_yield : :redis_key_ttl,
288
+ locks_released_at: block_given? ? ts : nil,
289
+ locks_acq_time: acq_time,
290
+ locks_hold_time: is_lock_manually_released ? hold_time : nil,
291
+ lock_series: lock_names,
292
+ rql_lock_series: lock_keys_for_instrumentation
293
+ }
294
+ else
295
+ yield_result
296
+ end
297
+ else
298
+ acquired_locks = successfully_acquired_locks.map { |state| state[:lock_name] }
299
+ missing_locks = lock_names - acquired_locks
300
+
301
+ {
302
+ ok: false,
303
+ result: {
304
+ error: :failed_to_acquire_lock_series,
305
+ detailed_errors: failed_locks_and_errors,
306
+ lock_series: lock_names,
307
+ acquired_locks:,
308
+ missing_locks:,
309
+ failed_on_lock:,
310
+ }
311
+ }
312
+ end
313
+ end
314
+ end
315
+ end
316
+ # steep:ignore
317
+ # 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
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 1.0.0
5
- # @version 1.14.0
5
+ # @version 1.16.0
6
6
  module RedisQueuedLocks::Utilities
7
7
  require_relative 'utilities/lock'
8
8
 
@@ -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.1
9
+ VERSION = '1.16.1'
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.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov
@@ -65,6 +65,9 @@ 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
69
+ - lib/redis_queued_locks/acquirer/lock_series_poc/instr_visitor.rb
70
+ - lib/redis_queued_locks/acquirer/lock_series_poc/log_visitor.rb
68
71
  - lib/redis_queued_locks/acquirer/locks.rb
69
72
  - lib/redis_queued_locks/acquirer/queue_info.rb
70
73
  - lib/redis_queued_locks/acquirer/queues.rb