redis_queued_locks 1.18.0 → 1.19.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 +4 -4
- data/.claude/project-overview.md +3 -2
- data/CHANGELOG.md +6 -0
- data/README.md +81 -2
- data/lib/redis_queued_locks/client.rb +19 -5
- data/lib/redis_queued_locks/version.rb +2 -2
- data/sig/redis_queued_locks/client.rbs +1 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e33f744b3798258657c7b699b078ff4017f0d21341a1103437734a97cc5a46e8
|
|
4
|
+
data.tar.gz: dacf22eae42a663ddf9984da0cda311f3b44f06b4a9e22d015eedc941b77ddbb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f7b7c4b1b38e633f7ee1fd3dbccd8846da716fa6756cf5a8279488cc75ebb93244adbccbd315844ad6a8590250450beb1487fd7b4b7d3c3441833a0f6d6b6d0f
|
|
7
|
+
data.tar.gz: 34189139e943bdae9e15d8642a8f2d504984547dd08a62efd92d27d786043e74808122c72dc24f25ccacc385862fe65e87249071f6e07e2925a72166ac3b2845
|
data/.claude/project-overview.md
CHANGED
|
@@ -49,7 +49,7 @@ duplicated on purpose (`Locks` duplicates the `LockInfo` write/read lock formatt
|
|
|
49
49
|
| `lock` / `lock!` | `acquirer/acquire_lock.rb` + `acquirer/acquire_lock/*` |
|
|
50
50
|
| `lock_series` / `lock_series!` | `acquirer/lock_series_poc.rb` (proof of concept; one `read_write_mode` for the whole series) |
|
|
51
51
|
| `unlock` | `acquirer/release_lock.rb` |
|
|
52
|
-
| `unlock_read` / `release_read_lock` | `acquirer/release_read_lock.rb` (own read lock only) |
|
|
52
|
+
| `unlock_read` / `release_read_lock` | `acquirer/release_read_lock.rb` (own read lock or `acquirer_id:` read lock only) |
|
|
53
53
|
| `extend_lock_ttl` | `acquirer/extend_lock_ttl.rb` (`read_write_mode: :read` extends own read lock, `+ all_read_locks: true` all live read locks) |
|
|
54
54
|
| `locked?` / `queued?` | `acquirer/is_locked.rb` / `acquirer/is_queued.rb` |
|
|
55
55
|
| `lock_info` / `queue_info` | `acquirer/lock_info.rb` / `acquirer/queue_info.rb` |
|
|
@@ -128,7 +128,8 @@ duplicated on purpose (`Locks` duplicates the `LockInfo` write/read lock formatt
|
|
|
128
128
|
after the block and on any failure (exception or `raise_errors: false`), once per failed series.
|
|
129
129
|
- Own read lock operations (current acquirer via `current_acquirer_id(identity:)`): `unlock_read` /
|
|
130
130
|
`release_read_lock` (`ZREM` + `DEL` reader data, event `explicit_read_lock_release`, result
|
|
131
|
-
`{ rel_time:, rel_key:, rel_acq_id:, lock_res: }`
|
|
131
|
+
`{ rel_time:, rel_key:, rel_acq_id:, lock_res: }`; `acquirer_id:` (default `nil` => current acquirer;
|
|
132
|
+
when passed, `identity:` is ignored: the acquirer id already contains its identity) releases the read lock of a different acquirer) and `extend_lock_ttl(..., read_write_mode: :read)`
|
|
132
133
|
(`multi(watch: [lock_key])`: extends only a live read lock while no write lock exists, never revives
|
|
133
134
|
an expired one; registry and reader data TTLs via `PEXPIRE NX`+`GT`). `unlock` releases everything.
|
|
134
135
|
- `extend_lock_ttl(..., read_write_mode: :read, all_read_locks: true)` (`false` by default, ignored for
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.19.0] - 2026-10-11
|
|
4
|
+
### Added
|
|
5
|
+
- `#unlock_read` (aka `#release_read_lock`): an ability to unlock the read lock of any acquirer
|
|
6
|
+
- `acquirer_id:` option (`nil` by default: the current acquirer) to release the read lock of a different acquirer
|
|
7
|
+
(`identity:` is ignored when `acquirer_id:` is passed: the acquirer id already contains its own identity);
|
|
8
|
+
|
|
3
9
|
## [1.18.0] - 2026-10-11
|
|
4
10
|
### Added
|
|
5
11
|
- Brand new feature - **Read/Write locks**: `read_write_mode:` attribute of `#lock`/`#lock!`/`#lock_series`/`#lock_series!` (`:write` by default):
|
data/README.md
CHANGED
|
@@ -1094,6 +1094,64 @@ rql.lock_info("your_lock_name")
|
|
|
1094
1094
|
}
|
|
1095
1095
|
```
|
|
1096
1096
|
|
|
1097
|
+
```ruby
|
|
1098
|
+
# <for read/write locks>
|
|
1099
|
+
# (see `read_write_mode:` kwarg attribute of #lock/#lock! methods and "Read/Write Locks" documentation section)
|
|
1100
|
+
# (lock requests are not included: use #queue_info to see the queued readers and writers)
|
|
1101
|
+
|
|
1102
|
+
# -> case 1: the lock is held by readers, writers and readers are waiting in the lock queues;
|
|
1103
|
+
|
|
1104
|
+
# process#1 and process#2 (readers, work in parallel):
|
|
1105
|
+
rql.lock("your_lock_name", read_write_mode: :read, ttl: 5_000, meta: { "kek" => "pek" }) { ... }
|
|
1106
|
+
rql.lock("your_lock_name", read_write_mode: :read, ttl: 3_000) { ... }
|
|
1107
|
+
# process#3 (writer, waits for all readers in the write lock queue):
|
|
1108
|
+
rql.lock("your_lock_name", read_write_mode: :write) { ... }
|
|
1109
|
+
# process#4 (reader, waits for the earlier writer in the read lock queue):
|
|
1110
|
+
rql.lock("your_lock_name", read_write_mode: :read) { ... }
|
|
1111
|
+
|
|
1112
|
+
rql.lock_info("your_lock_name")
|
|
1113
|
+
|
|
1114
|
+
# => (read locks info: process#1 and process#2)
|
|
1115
|
+
{
|
|
1116
|
+
"lock_key" => "rql:lock:your_lock_name",
|
|
1117
|
+
"rw_mode" => "read",
|
|
1118
|
+
"rem_ttl" => 4_985, # remaining ttl of the longest read lock
|
|
1119
|
+
"readers" => [
|
|
1120
|
+
{
|
|
1121
|
+
"acq_id" => "rql:acq:123/456/567/678/374dd74324",
|
|
1122
|
+
"hst_id" => "rql:hst:123/456/678/374dd74324",
|
|
1123
|
+
"ts" => 123456789.12345,
|
|
1124
|
+
"ini_ttl" => 5_000,
|
|
1125
|
+
"rem_ttl" => 4_985,
|
|
1126
|
+
"kek" => "pek" # custom metadata of the read lock
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
"acq_id" => "rql:acq:124/457/568/679/a83cb4f9d1",
|
|
1130
|
+
"hst_id" => "rql:hst:124/457/679/a83cb4f9d1",
|
|
1131
|
+
"ts" => 123456789.54321,
|
|
1132
|
+
"ini_ttl" => 3_000,
|
|
1133
|
+
"rem_ttl" => 2_412
|
|
1134
|
+
}
|
|
1135
|
+
]
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
# -> case 2: (some time later) readers are released, the lock is held by the writer (process#3),
|
|
1139
|
+
# the reader (process#4) is still waiting in the read lock queue;
|
|
1140
|
+
|
|
1141
|
+
rql.lock_info("your_lock_name")
|
|
1142
|
+
|
|
1143
|
+
# => (write lock info: process#3, read lock requests are not included)
|
|
1144
|
+
{
|
|
1145
|
+
"lock_key" => "rql:lock:your_lock_name",
|
|
1146
|
+
"rw_mode" => "write",
|
|
1147
|
+
"acq_id" => "rql:acq:125/458/569/680/c7bfcaf4f9",
|
|
1148
|
+
"hst_id" => "rql:hst:125/458/680/c7bfcaf4f9",
|
|
1149
|
+
"ts" => 123456795.12345,
|
|
1150
|
+
"ini_ttl" => 5_000,
|
|
1151
|
+
"rem_ttl" => 4_870
|
|
1152
|
+
}
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1097
1155
|
---
|
|
1098
1156
|
|
|
1099
1157
|
#### #queue_info
|
|
@@ -1253,10 +1311,15 @@ rql.unlock("your_lock_name")
|
|
|
1253
1311
|
- suitable for read locks obtained without a block of code (`lock(..., read_write_mode: :read)` without a block);
|
|
1254
1312
|
- the current acquirer is calculated in the same way as for `#lock` (`#current_acquirer_id`: process, thread, fiber, ractor, identity),
|
|
1255
1313
|
so call it from the same thread/fiber that obtained the read lock;
|
|
1314
|
+
- the read lock of a different acquirer can be released via `:acquirer_id` (for example, extracted from `#lock_info`/`#locks_info`);
|
|
1315
|
+
`:identity` is ignored in this case (the acquirer id already contains its own identity);
|
|
1256
1316
|
- has an alias: `#release_read_lock`;
|
|
1257
1317
|
- accepts:
|
|
1258
1318
|
- `lock_name` - (required) `[String]` - the lock name whose read lock should be released;
|
|
1319
|
+
- `:acquirer_id` - (optional) `[String, NilClass]` - the acquirer whose read lock should be released
|
|
1320
|
+
(`nil` by default: the current acquirer is used);
|
|
1259
1321
|
- `:identity` - (optional) `[String]` - the same identity as for `#lock` (`uniq_identity` by default);
|
|
1322
|
+
ignored when `:acquirer_id` is passed: the acquirer id already contains its own identity;
|
|
1260
1323
|
- `:logger`, `:instrumenter`, `:instrument`, `:log_sampling_enabled`, `:log_sampling_percent`, `:log_sampler`, `:log_sample_this`,
|
|
1261
1324
|
`:instr_sampling_enabled`, `:instr_sampling_percent`, `:instr_sampler`, `:instr_sample_this` - (optional) - the same as for `#unlock`;
|
|
1262
1325
|
- returns `{ ok: true, result: { rel_time:, rel_key:, rel_acq_id:, lock_res: } }` (`lock_res` - `:released` or `:nothing_to_release`);
|
|
@@ -1278,6 +1341,22 @@ rql.unlock_read('report')
|
|
|
1278
1341
|
}
|
|
1279
1342
|
```
|
|
1280
1343
|
|
|
1344
|
+
```ruby
|
|
1345
|
+
# release the read lock of a different acquirer
|
|
1346
|
+
reader = rql.lock_info('report')['readers'].first
|
|
1347
|
+
rql.unlock_read('report', acquirer_id: reader['acq_id'])
|
|
1348
|
+
# =>
|
|
1349
|
+
{
|
|
1350
|
+
ok: true,
|
|
1351
|
+
result: {
|
|
1352
|
+
rel_time: 0.29, # milliseconds
|
|
1353
|
+
rel_key: 'rql:lock:report',
|
|
1354
|
+
rel_acq_id: 'rql:acq:17580/2260/2380/2280/3f16b93973612580', # reader['acq_id']
|
|
1355
|
+
lock_res: :released # or :nothing_to_release
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
```
|
|
1359
|
+
|
|
1281
1360
|
---
|
|
1282
1361
|
|
|
1283
1362
|
#### #clear_locks - release all locks and lock queues
|
|
@@ -2237,8 +2316,8 @@ rql.lock('report') { rebuild_report } # (write mode is the default mode)
|
|
|
2237
2316
|
initial ttl, `:meta` (with the same reserved keys) and reentrant lock data (`spc_cnt`, `spc_ext_ttl`, ...);
|
|
2238
2317
|
the read lock data lives as long as the read lock and is returned by `#lock_info`/`#locks_info` for each reader;
|
|
2239
2318
|
- `#unlock` releases the lock entirely: the write lock, all read locks (with their data) and all lock requests;
|
|
2240
|
-
- `#unlock_read` (aka `#release_read_lock`) releases the read lock of the current acquirer
|
|
2241
|
-
(suitable for read locks obtained without a block)
|
|
2319
|
+
- `#unlock_read` (aka `#release_read_lock`) releases the read lock of the current acquirer
|
|
2320
|
+
(suitable for read locks obtained without a block), or the read lock of the acquirer passed via `acquirer_id:`;
|
|
2242
2321
|
- `#extend_lock_ttl(lock_name, milliseconds, read_write_mode: :read)` extends the read lock of the current acquirer only;
|
|
2243
2322
|
`all_read_locks: true` extends all live read locks of the lock;
|
|
2244
2323
|
- `#lock_series` with `read_write_mode: :read` obtains a series of read locks (the mode is the same for all locks of the series):
|
|
@@ -677,14 +677,20 @@ class RedisQueuedLocks::Client
|
|
|
677
677
|
end
|
|
678
678
|
alias_method :release_lock, :unlock
|
|
679
679
|
|
|
680
|
-
# Releases the read lock of the current acquirer
|
|
681
|
-
#
|
|
682
|
-
# read locks obtained without
|
|
680
|
+
# Releases the read lock of the current acquirer (or of the custom acquirer passed via
|
|
681
|
+
# `acquirer_id:`) only: other read locks, the write lock and lock requests are not affected
|
|
682
|
+
# (`#unlock` releases the lock entirely). It is suitable for read locks obtained without
|
|
683
|
+
# a block of code.
|
|
683
684
|
#
|
|
684
685
|
# @param lock_name [String] The lock name whose read lock should be released.
|
|
686
|
+
# @option acquirer_id [String,NilClass]
|
|
687
|
+
# - The acquirer whose read lock should be released (`nil` by default);
|
|
688
|
+
# - `nil` => the current acquirer is used (calculated from `identity:`, see `#current_acquirer_id`);
|
|
689
|
+
# - can be extracted from the lock data (`#lock_info`, `#locks_info`, `#queue_info`, `#queues_info`);
|
|
685
690
|
# @option identity [String]
|
|
686
691
|
# Unique acquirer identity (the same as for `#lock`) that is used to calculate the current
|
|
687
|
-
# acquirer id (see `#current_acquirer_id`).
|
|
692
|
+
# acquirer id (see `#current_acquirer_id`). Ignored when `acquirer_id:` is passed: the acquirer id
|
|
693
|
+
# already contains its own identity.
|
|
688
694
|
# @option logger [::Logger,#debug]
|
|
689
695
|
# @option instrumenter [#notify]
|
|
690
696
|
# @option instrument [NilClass,Any]
|
|
@@ -707,10 +713,18 @@ class RedisQueuedLocks::Client
|
|
|
707
713
|
# }
|
|
708
714
|
# }
|
|
709
715
|
#
|
|
716
|
+
# @example Release the read lock of the current acquirer:
|
|
717
|
+
# client.unlock_read('report')
|
|
718
|
+
#
|
|
719
|
+
# @example Release the read lock of a different acquirer:
|
|
720
|
+
# client.unlock_read('report', acquirer_id: "rql:acq:62681/2016/2024/2032/b30ec5e4bea10512")
|
|
721
|
+
#
|
|
710
722
|
# @api public
|
|
711
723
|
# @since 1.18.0
|
|
724
|
+
# @version 1.19.0
|
|
712
725
|
def unlock_read(
|
|
713
726
|
lock_name,
|
|
727
|
+
acquirer_id: nil,
|
|
714
728
|
identity: uniq_identity,
|
|
715
729
|
logger: config['logger'],
|
|
716
730
|
instrumenter: config['instrumenter'],
|
|
@@ -727,7 +741,7 @@ class RedisQueuedLocks::Client
|
|
|
727
741
|
RedisQueuedLocks::Acquirer::ReleaseReadLock.release_read_lock(
|
|
728
742
|
redis_client,
|
|
729
743
|
lock_name,
|
|
730
|
-
current_acquirer_id(identity:),
|
|
744
|
+
acquirer_id || current_acquirer_id(identity:),
|
|
731
745
|
instrumenter,
|
|
732
746
|
logger,
|
|
733
747
|
instrument,
|