redis_queued_locks 0.0.22 → 0.0.23
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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 05a8fc14cae41bf9870bedf4eae9fa298c3704b57d25df428c2e2c8bd9bf0223
|
4
|
+
data.tar.gz: 14a941048edc7679df1d80805a96d3db4019d45acfc80ac5444d99e12cc3eadc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 292f4ed74242a1a53b96e290e7759db0b1c918070f0875805bedbe78360fac71b6ac81ec09b63836002b2a17ab247ca06f9cf7ff828a4a07f924b2588e24fd3f
|
7
|
+
data.tar.gz: 270749b6a32418e2a57b3995b5ef3b826b6066a97a56efd08f9305f0ed314ed7da13f9f395f891ab619ceea3d4bde53c880278758fbf42fb1e84b98fa5b14690
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.0.23] - 2024-03-21
|
4
|
+
### Changed
|
5
|
+
- Composed redis commands are invoked on the one conenction
|
6
|
+
(instead of mutiple connection fetchiong from redis connection pool on each redis command);
|
7
|
+
|
3
8
|
## [0.0.22] - 2024-03-21
|
4
9
|
### Added
|
5
10
|
- Logging infrastructure. Initial implementation includes the only debugging features.
|
@@ -31,95 +31,101 @@ module RedisQueuedLocks::Acquier::AcquireLock::TryToLock
|
|
31
31
|
timestamp = nil
|
32
32
|
|
33
33
|
# Step 0: watch the lock key changes (and discard acquirement if lock is already acquired)
|
34
|
-
result = redis.
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
RedisQueuedLocks.debug(
|
44
|
-
"Step №1: добавление в очередь (#{acquier_id}). [ZADD to the queue: #{res}]"
|
45
|
-
)
|
46
|
-
|
47
|
-
# Step 2.1: drop expired acquiers from the lock queue
|
48
|
-
res = redis.call(
|
49
|
-
'ZREMRANGEBYSCORE',
|
50
|
-
lock_key_queue,
|
51
|
-
'-inf',
|
52
|
-
RedisQueuedLocks::Resource.acquier_dead_score(queue_ttl)
|
53
|
-
)
|
54
|
-
|
55
|
-
RedisQueuedLocks.debug(
|
56
|
-
"Step №2: дропаем из очереди просроченных ожидающих. [ZREMRANGE: #{res}]"
|
57
|
-
)
|
58
|
-
|
59
|
-
# Step 3: get the actual acquier waiting in the queue
|
60
|
-
waiting_acquier = Array(redis.call('ZRANGE', lock_key_queue, '0', '0')).first
|
61
|
-
|
62
|
-
RedisQueuedLocks.debug(
|
63
|
-
"Step №3: какой процесс в очереди сейчас ждет. " \
|
64
|
-
"[ZRANGE <следующий процесс>: #{waiting_acquier} :: <текущий процесс>: #{acquier_id}]"
|
65
|
-
)
|
66
|
-
|
67
|
-
# Step 4: check the actual acquier: is it ours? are we aready to lock?
|
68
|
-
unless waiting_acquier == acquier_id
|
69
|
-
# Step ROLLBACK 1.1: our time hasn't come yet. retry!
|
34
|
+
result = redis.with do |rconn|
|
35
|
+
rconn.multi(watch: [lock_key]) do |transact|
|
36
|
+
# Fast-Step X0: fail-fast check
|
37
|
+
if fail_fast && rconn.call('HGET', lock_key, 'acq_id')
|
38
|
+
# Fast-Step X1: is lock already obtained. fail fast - no try.
|
39
|
+
inter_result = :fail_fast_no_try
|
40
|
+
else
|
41
|
+
# Step 1: add an acquier to the lock acquirement queue
|
42
|
+
res = rconn.call('ZADD', lock_key_queue, 'NX', acquier_position, acquier_id)
|
70
43
|
|
71
44
|
RedisQueuedLocks.debug(
|
72
|
-
"Step
|
73
|
-
"[Ждет: #{waiting_acquier}. А нужен: #{acquier_id}]"
|
45
|
+
"Step №1: добавление в очередь (#{acquier_id}). [ZADD to the queue: #{res}]"
|
74
46
|
)
|
75
47
|
|
76
|
-
|
77
|
-
|
78
|
-
|
48
|
+
# Step 2.1: drop expired acquiers from the lock queue
|
49
|
+
res = rconn.call(
|
50
|
+
'ZREMRANGEBYSCORE',
|
51
|
+
lock_key_queue,
|
52
|
+
'-inf',
|
53
|
+
RedisQueuedLocks::Resource.acquier_dead_score(queue_ttl)
|
54
|
+
)
|
55
|
+
|
56
|
+
RedisQueuedLocks.debug(
|
57
|
+
"Step №2: дропаем из очереди просроченных ожидающих. [ZREMRANGE: #{res}]"
|
58
|
+
)
|
79
59
|
|
80
|
-
# Step
|
81
|
-
|
60
|
+
# Step 3: get the actual acquier waiting in the queue
|
61
|
+
waiting_acquier = Array(rconn.call('ZRANGE', lock_key_queue, '0', '0')).first
|
82
62
|
|
83
63
|
RedisQueuedLocks.debug(
|
84
|
-
"
|
85
|
-
"[
|
86
|
-
"#{(locked_by_acquier == nil) ? 'не занят' : "занят процессом <#{locked_by_acquier}>"}"
|
64
|
+
"Step №3: какой процесс в очереди сейчас ждет. " \
|
65
|
+
"[ZRANGE <следующий процесс>: #{waiting_acquier} :: <текущий процесс>: #{acquier_id}]"
|
87
66
|
)
|
88
67
|
|
89
|
-
|
90
|
-
|
68
|
+
# Step 4: check the actual acquier: is it ours? are we aready to lock?
|
69
|
+
unless waiting_acquier == acquier_id
|
70
|
+
# Step ROLLBACK 1.1: our time hasn't come yet. retry!
|
91
71
|
|
92
72
|
RedisQueuedLocks.debug(
|
93
|
-
"Step ROLLBACK №
|
94
|
-
"[
|
73
|
+
"Step ROLLBACK №1: не одинаковые ключи. выходим. " \
|
74
|
+
"[Ждет: #{waiting_acquier}. А нужен: #{acquier_id}]"
|
95
75
|
)
|
96
76
|
|
97
|
-
inter_result = :
|
77
|
+
inter_result = :acquier_is_not_first_in_queue
|
98
78
|
else
|
99
|
-
# NOTE:
|
79
|
+
# NOTE: our time has come! let's try to acquire the lock!
|
100
80
|
|
101
|
-
# Step
|
102
|
-
|
81
|
+
# Step 5: check if the our lock is already acquired
|
82
|
+
locked_by_acquier = rconn.call('HGET', lock_key, 'acq_id')
|
103
83
|
|
84
|
+
# rubocop:disable Layout/LineLength
|
104
85
|
RedisQueuedLocks.debug(
|
105
|
-
|
86
|
+
"Ste №5: Ищем требуемый лок. " \
|
87
|
+
"[HGET<#{lock_key}>: " \
|
88
|
+
"#{(locked_by_acquier == nil) ? 'не занят' : "занят процессом <#{locked_by_acquier}>"}"
|
106
89
|
)
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
90
|
+
# rubocop:enable Layout/LineLength
|
91
|
+
|
92
|
+
if locked_by_acquier
|
93
|
+
# Step ROLLBACK 2: required lock is stil acquired. retry!
|
94
|
+
|
95
|
+
RedisQueuedLocks.debug(
|
96
|
+
"Step ROLLBACK №2: Ключ уже занят. Ничего не делаем. " \
|
97
|
+
"[Занят процессом: #{locked_by_acquier}]"
|
98
|
+
)
|
99
|
+
|
100
|
+
inter_result = :lock_is_still_acquired
|
101
|
+
else
|
102
|
+
# NOTE: required lock is free and ready to be acquired! acquire!
|
103
|
+
|
104
|
+
# Step 6.1: remove our acquier from waiting queue
|
105
|
+
transact.call('ZPOPMIN', lock_key_queue, '1')
|
106
|
+
|
107
|
+
RedisQueuedLocks.debug(
|
108
|
+
'Step №4: Забираем наш текущий процесс из очереди. [ZPOPMIN]'
|
109
|
+
)
|
110
|
+
|
111
|
+
# rubocop:disable Layout/LineLength
|
112
|
+
RedisQueuedLocks.debug(
|
113
|
+
"===> <FINAL> Step №6: закрепляем лок за процессом [HSET<#{lock_key}>: #{acquier_id}]"
|
114
|
+
)
|
115
|
+
# rubocop:enable Layout/LineLength
|
116
|
+
|
117
|
+
# Step 6.2: acquire a lock and store an info about the acquier
|
118
|
+
transact.call(
|
119
|
+
'HSET',
|
120
|
+
lock_key,
|
121
|
+
'acq_id', acquier_id,
|
122
|
+
'ts', (timestamp = Time.now.to_i),
|
123
|
+
'ini_ttl', ttl
|
124
|
+
)
|
125
|
+
|
126
|
+
# Step 6.3: set the lock expiration time in order to prevent "infinite locks"
|
127
|
+
transact.call('PEXPIRE', lock_key, ttl) # NOTE: in milliseconds
|
128
|
+
end
|
123
129
|
end
|
124
130
|
end
|
125
131
|
end
|
@@ -57,26 +57,28 @@ module RedisQueuedLocks::Acquier::ReleaseAllLocks
|
|
57
57
|
# @api private
|
58
58
|
# @since 0.1.0
|
59
59
|
def fully_release_all_locks(redis, batch_size)
|
60
|
-
result = redis.
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
60
|
+
result = redis.with do |rconn|
|
61
|
+
rconn.pipelined do |pipeline|
|
62
|
+
# Step A: release all queus and their related locks
|
63
|
+
rconn.scan(
|
64
|
+
'MATCH',
|
65
|
+
RedisQueuedLocks::Resource::LOCK_QUEUE_PATTERN,
|
66
|
+
count: batch_size
|
67
|
+
) do |lock_queue|
|
68
|
+
# TODO: reduce unnecessary iterations
|
69
|
+
pipeline.call('ZREMRANGEBYSCORE', lock_queue, '-inf', '+inf')
|
70
|
+
pipeline.call('EXPIRE', RedisQueuedLocks::Resource.lock_key_from_queue(lock_queue), '0')
|
71
|
+
end
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
73
|
+
# Step B: release all locks
|
74
|
+
rconn.scan(
|
75
|
+
'MATCH',
|
76
|
+
RedisQueuedLocks::Resource::LOCK_PATTERN,
|
77
|
+
count: batch_size
|
78
|
+
) do |lock_key|
|
79
|
+
# TODO: reduce unnecessary iterations
|
80
|
+
pipeline.call('EXPIRE', lock_key, '0')
|
81
|
+
end
|
80
82
|
end
|
81
83
|
end
|
82
84
|
|
@@ -65,9 +65,11 @@ module RedisQueuedLocks::Acquier::ReleaseLock
|
|
65
65
|
# @api private
|
66
66
|
# @since 0.1.0
|
67
67
|
def fully_release_lock(redis, lock_key, lock_key_queue)
|
68
|
-
result = redis.
|
69
|
-
|
70
|
-
|
68
|
+
result = redis.with do |rconn|
|
69
|
+
rconn.multi do |transact|
|
70
|
+
transact.call('ZREMRANGEBYSCORE', lock_key_queue, '-inf', '+inf')
|
71
|
+
transact.call('EXPIRE', lock_key, '0')
|
72
|
+
end
|
71
73
|
end
|
72
74
|
|
73
75
|
RedisQueuedLocks::Data[ok: true, result:]
|