Skip to main content

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 timeout parameter: 0 = wait forever, -1 = skip if locked, N = wait up to N seconds.
  • Silent skip — cl() + silence_object_lock_timeout=True skips the block body without try/except when 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.Redis client or an iterable of clients for true multi-master Redlock quorum. Defaults to redis.Redis().
  • Async support — ConfigurableAIOREDLock provides the same API for async with / await usage.

Documentation

Full documentation: https://docs.velis.si/configurable-redlock.html

Why not just use pottery's Redlock?

pottery's Redlock already covers distributed locking with TTL, timeout, and skip-if-taken. The two things ConfigurableREDLock adds are a cleaner API for single-Redis setups (one timeout parameter instead of context_manager_blocking + context_manager_timeout) and two features pottery cannot provide:

  1. Silent skip — skip the with block body without a try/except around it (see below)
  2. 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 to cl() 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 a with block. Calling cl() as the first statement is the mechanism that skips the rest of the block when the lock was not acquired — it raises ObjectLockTimeout, which immediately exits the block. If you forget the call entirely, exiting the with block raises NoTimeoutCheck as 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 processes
  • ConfigurableLockKeys — 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.

Metadata

Release files for configurable-redlock 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for configurable-redlock 1.0.0
File Size Uploaded Provenance Actions
configurable_redlock-1.0.0.tar.gz 8.3 kB Without provenance Details for configurable_redlock-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for configurable-redlock 1.0.0
File Interpreter ABI Platform Actions
configurable_redlock-1.0.0-py3-none-any.whl Python 3 none any Details for configurable_redlock-1.0.0-py3-none-any.whl

Total release size: 17.2 kB

Release files / configurable_redlock-1.0.0.tar.gz

Download URL configurable_redlock-1.0.0.tar.gz
Size 8.3 kB
Tags Source
SHA-256 checksum
How to use checksums
5ede559388c75a332a234caeec27272c642b3bcd14e01181a392415b68adc5bc
BLAKE2b-256 checksum
How to use checksums
acbb06cd96343e0fbd33983af0d3ef09af76a96478d8a70839c52c1866544db3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.2

Release files / configurable_redlock-1.0.0-py3-none-any.whl

Download URL configurable_redlock-1.0.0-py3-none-any.whl
Size 8.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
52bbf0b4c4a280143f0b871ce17aa6f127277791ff31b8bffad6a872cc7dbee1
BLAKE2b-256 checksum
How to use checksums
a81c53e2996407541bad6d50c6966a6d87a65fe73154a989a6477a417b26ae67
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.2

Security for configurable-redlock 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page