Simplified Redlock API with configurable TTL and silent exception handling for deadlock prevention
Project description
configurable-redlock
A Python distributed lock built on top of pottery's Redlock implementation. It wraps pottery's Redlock with a simpler timeout API and adds two features that pottery cannot provide natively: silent skip (skip a with block body without try/except) and waiting statistics (Redis counters tracking how many processes are waiting per lock key).
Features
- Distributed locking via Redis — uses pottery's Redlock algorithm, safe across multiple processes and machines, with automatic TTL (
auto_release_time, default 30 s) to prevent deadlocks on process crash. - Simple timeout API — one
timeoutparameter:0= wait forever,-1= skip if locked,N= wait up to N seconds. - Silent skip —
cl()+silence_object_lock_timeout=Trueskips the block body withouttry/exceptwhen the lock is not acquired. - Waiting statistics — while a process waits for the lock, the number of waiters and the set of contended lock keys are tracked in Redis, allowing external monitoring of lock contention.
- Configurable Redis client — pass a single
redis.Redisclient or an iterable of clients for true multi-master Redlock quorum. Defaults toredis.Redis(). - Async support —
ConfigurableAIOREDLockprovides the same API forasync with/awaitusage.
Documentation
Full documentation: https://docs.velis.si/configurable-redlock.html
Why not just use pottery's Redlock?
pottery's
Redlockalready covers distributed locking with TTL, timeout, and skip-if-taken. The two thingsConfigurableREDLockadds are a cleaner API for single-Redis setups (onetimeoutparameter instead ofcontext_manager_blocking+context_manager_timeout) and two features pottery cannot provide:
- Silent skip — skip the
withblock body without atry/exceptaround it (see below)- Waiting statistics — Redis counters tracking how many processes are waiting per lock key
The
cl()call exists solely to enable silent skip: Python's context manager protocol does not allow__enter__to skip the block body, and raising from__enter__prevents__exit__from running, which means the exception can never be silenced. By deferring the raise tocl()inside the block,__exit__always runs and can swallow the exception.
Comparison with pottery Redlock
Timeout
pottery:
from pottery import Redlock
from pottery.exceptions import QuorumNotAchieved
try:
with Redlock(key='res', masters={redis}, context_manager_blocking=True, context_manager_timeout=5):
do_work()
except QuorumNotAchieved:
pass # lock not acquired within 5 seconds
ConfigurableREDLock:
from configurable_redlock import ConfigurableREDLock, ObjectLockTimeout
try:
with ConfigurableREDLock(name='res', timeout=5) as cl:
cl()
do_work()
except ObjectLockTimeout:
pass # lock not acquired within 5 seconds
Silent skip (no try/except)
pottery — not possible without try/except:
from pottery.exceptions import QuorumNotAchieved
try:
with Redlock(key='res', masters={redis}, context_manager_blocking=False):
do_work()
except QuorumNotAchieved:
pass # must always handle explicitly
ConfigurableREDLock:
with ConfigurableREDLock(name='res', timeout=-1, silence_object_lock_timeout=True) as cl:
cl() # silently skips everything below if lock not acquired — no try/except needed
do_work()
Installation
pip install configurable-redlock
Usage
from configurable_redlock import ConfigurableREDLock
ConfigurableREDLock is used as a context manager. The timeout parameter controls what happens when the lock
is already held by another process.
timeout=0 — wait forever
The process blocks until the lock becomes available. No cl() call is needed.
with ConfigurableREDLock(name="my-resource") as cl:
# lock is always acquired here
do_work()
timeout=-1 — skip if locked
The idiomatic way to use timeout=-1 is together with silence_object_lock_timeout=True. With this
combination, cl() raises ObjectLockTimeout when the lock is not acquired, and the exception is
silently swallowed on exit — no try/except needed, the rest of the block is simply skipped.
with ConfigurableREDLock(name="my-resource", timeout=-1, silence_object_lock_timeout=True) as cl:
cl() # skips everything below if lock was not acquired
do_work()
Moving cl() down allows some code to run before the skip — useful for logging that the context
was entered regardless of whether the lock was acquired:
with ConfigurableREDLock(name="my-resource", timeout=-1, silence_object_lock_timeout=True) as cl:
log.debug("entered my-resource block")
cl() # skips everything below if lock was not acquired
do_work()
If you need to distinguish the "skipped" case from success, catch ObjectLockTimeout explicitly
instead of using silence_object_lock_timeout:
from configurable_redlock import ObjectLockTimeout
try:
with ConfigurableREDLock(name="my-resource", timeout=-1) as cl:
cl()
do_work()
except ObjectLockTimeout:
log.debug("skipped: lock already held")
timeout=N — wait up to N seconds
The process waits at most N seconds. Call cl() as the first line — it raises ObjectLockTimeout if
the lock could not be acquired within the given time.
from configurable_redlock import ObjectLockTimeout
try:
with ConfigurableREDLock(name="my-resource", timeout=5) as cl:
cl() # raises ObjectLockTimeout if N seconds elapsed without acquiring
do_work()
except ObjectLockTimeout:
pass # could not acquire within 5 seconds
Why is
cl()needed for non-zero timeouts? Python's context manager protocol does not allow__enter__to skip the body of awithblock. Callingcl()as the first statement is the mechanism that skips the rest of the block when the lock was not acquired — it raisesObjectLockTimeout, which immediately exits the block. If you forget the call entirely, exiting thewithblock raisesNoTimeoutCheckas a reminder.
Silencing ObjectLockTimeout on exit
If you want ObjectLockTimeout raised inside the block to not propagate out:
with ConfigurableREDLock(name="my-resource", timeout=-1, silence_object_lock_timeout=True) as cl:
cl()
raise ObjectLockTimeout() # swallowed on exit
Parameters
| Parameter | Default | Description |
|---|---|---|
name |
required | Lock identifier. Stored in Redis as ConfigurableLock.<name>. |
timeout |
0 |
0 = wait forever, -1 = skip if locked, N = wait up to N seconds. |
auto_release_time |
30.0 |
Lock TTL in seconds. The lock is automatically released after this time, preventing deadlocks if the process crashes. |
silence_object_lock_timeout |
False |
If True, an ObjectLockTimeout raised inside the with block is suppressed on exit. |
stats_name |
name |
Override the name used for waiting-process statistics in Redis. |
redis_client |
redis.Redis() |
A single Redis client or an iterable of clients for multi-master Redlock. Defaults to a localhost connection. |
Waiting statistics
While a process waits to acquire the lock, ConfigurableREDLock tracks the number of waiting processes in Redis:
Waiting.ConfigurableLock.<name>— a counter of currently waiting processesConfigurableLockKeys— a Redis list of all keys that have ever had waiters
This allows external monitoring of lock contention.
Exceptions
| Exception | When raised |
|---|---|
ObjectLockTimeout |
Raised by cl() when the lock was not acquired (timeout=-1 or timeout=N expired). |
NoTimeoutCheck |
Raised on __exit__ when timeout != 0 and cl() was never called inside the block. |
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file configurable_redlock-1.0.0.tar.gz.
File metadata
- Download URL: configurable_redlock-1.0.0.tar.gz
- Upload date:
- Size: 8.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5ede559388c75a332a234caeec27272c642b3bcd14e01181a392415b68adc5bc
|
|
| MD5 |
d77448b6289a813595333ac0cc5c683b
|
|
| BLAKE2b-256 |
acbb06cd96343e0fbd33983af0d3ef09af76a96478d8a70839c52c1866544db3
|
File details
Details for the file configurable_redlock-1.0.0-py3-none-any.whl.
File metadata
- Download URL: configurable_redlock-1.0.0-py3-none-any.whl
- Upload date:
- Size: 8.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
52bbf0b4c4a280143f0b871ce17aa6f127277791ff31b8bffad6a872cc7dbee1
|
|
| MD5 |
e0cd429f824c83835a2a73b34e018e4a
|
|
| BLAKE2b-256 |
a81c53e2996407541bad6d50c6966a6d87a65fe73154a989a6477a417b26ae67
|