RedisEventIsolation
extends BaseEventIsolation
in package
Redis-backed FSM event isolation via a distributed SET NX PX lock.
Mirrors aiogram.fsm.storage.redis.RedisEventIsolation
(aiogram/fsm/storage/redis.py). Because amphp/redis ^2 does not
expose a built-in distributed lock primitive with an async-context-manager
interface, this class implements the lock protocol manually:
Acquire — SET $lockKey $token NX PX $ttlMs
NXensures only one client sets the key (atomic compare-and-set).PX(millisecond TTL) guarantees the lock auto-expires if the holder crashes, preventing deadlocks.- On failure the caller sleeps 50 ms and retries until the TTL budget is
consumed, after which a
RuntimeExceptionis thrown.
Release — Lua CHECK-AND-DELETE
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
This atomically ensures that only the token owner can delete the key, preventing accidental release of a lock acquired by another client after the original lock expired.
The lock is surfaced as a Lock value-object whose release() wires the
Lua check-and-delete via an optional $releaseFn closure (Task 5.3
extension).
close() is a no-op: the owning RedisStorage manages the connection
lifecycle.
Table of Contents
Constants
- RETRY_DELAY_SECONDS : mixed = 0.05
- Poll interval in seconds (50 ms) between acquire retries.
- UNLOCK_SCRIPT : mixed = <<<'LUA' if redis.call('get', KEYS[1]) == ARGV[...
- Lua script for safe lock release.
Properties
- $acquireTimeoutSeconds : int|null
- $keyBuilder : KeyBuilder
- $lockTtlSeconds : int
- $redis : RedisClient
Methods
- __construct() : mixed
- close() : void
- No-op — the `RedisClient` connection is owned by the storage layer.
- lock() : Lock
- Acquire a distributed lock for `$key`.
- acquireTimeout() : int
- Resolve the effective acquire-timeout budget in seconds.
Constants
RETRY_DELAY_SECONDS
Poll interval in seconds (50 ms) between acquire retries.
private
mixed
RETRY_DELAY_SECONDS
= 0.05
UNLOCK_SCRIPT
Lua script for safe lock release.
private
mixed
UNLOCK_SCRIPT
= <<<'LUA'
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
LUA
Only deletes the key when the stored value matches the caller's token, preventing a client from releasing a lock it no longer owns (e.g. after TTL expiry and re-acquisition by another client).
Properties
$acquireTimeoutSeconds read-only
private
int|null
$acquireTimeoutSeconds
= null
$keyBuilder read-only
private
KeyBuilder
$keyBuilder
= new DefaultKeyBuilder()
$lockTtlSeconds read-only
private
int
$lockTtlSeconds
= 60
$redis read-only
private
RedisClient
$redis
Methods
__construct()
public
__construct(RedisClient $redis[, KeyBuilder $keyBuilder = new DefaultKeyBuilder() ][, int $lockTtlSeconds = 60 ][, null|int $acquireTimeoutSeconds = null ]) : mixed
Parameters
- $redis : RedisClient
-
amphp/redis client instance (shared with storage).
- $keyBuilder : KeyBuilder = new DefaultKeyBuilder()
-
Key-builder strategy; defaults to
DefaultKeyBuilder. - $lockTtlSeconds : int = 60
-
Lock TTL in seconds. After this duration the lock auto-expires so a crashed holder cannot block others forever.
- $acquireTimeoutSeconds : null|int = null
-
Maximum wall-clock seconds the acquire loop will spin before giving up with a
RuntimeException. Defaults to$lockTtlSeconds * 2so that a slow holder (still within its TTL) does not starve waiting callers.
close()
No-op — the `RedisClient` connection is owned by the storage layer.
public
close() : void
Mirrors RedisEventIsolation.close (upstream redis.py).
lock()
Acquire a distributed lock for `$key`.
public
lock(StorageKey $key) : Lock
Spins until SET NX PX succeeds or the acquire-timeout budget is exhausted.
Returns a Lock whose release() executes the safe Lua unlock script.
Parameters
- $key : StorageKey
-
Storage address to lock.
Tags
Return values
Lock —Acquired lock; call release() in a finally block.
acquireTimeout()
Resolve the effective acquire-timeout budget in seconds.
private
acquireTimeout() : int
When $acquireTimeoutSeconds is null (the default), falls back to
$lockTtlSeconds * 2 so that waiting callers outlast the holder's TTL.