Redis-based distributed synchronization primitives for Python
Project description
redsync
Redis-based distributed synchronization primitives for Python. Async API using redis.asyncio.
Features
- Blocking, no polling – Uses Redis
BLPOP: the connection blocks on the server until a permit is available. No busy-waiting, no lock + pub/sub overhead. - Async-first – Built on
redis.asyncio; use withasync/awaitand context managers. - Configurable init – LUA (atomic, default) or SETNX strategy for creating the permit pool.
- N permits – Semaphore count from 1 to 4096 for limiting concurrency across processes.
- Python 3.10+ – Modern Python support.
TODO
- Semaphore delete / lifecycle
- Option A: set expire time on the list key (simple; semaphore disappears when unused).
- Option B: async background task that extends TTL while at least one semaphore instance exists (keeps it alive as long as someone uses it).
- Consider other algorithms (e.g. refcount in metadata, lease-based cleanup).
- Creator-only count – Only the creator sets
count; other callers wait until the semaphore exists and then read metadata (count, etc.) instead of passing count. - Maybe List vs sorted set – Evaluate whether Redis sorted sets are a better fit than a list (e.g. per-permit TTL, ordering, or different blocking semantics).
- Other sync primitives – Add more primitives (e.g. event).
Installation
pip install redsync
Or with uv:
uv add redsync
Requirements: Redis server, redis>=5.0.0 (async support).
Semaphore
Usage
import asyncio
from redis.asyncio import Redis
from redsync import RedisSemaphore, RedisSemaphoreTimeoutError
async def main():
r = Redis()
sem = await RedisSemaphore.create(r, "my_resource", count=1)
# acquire() raises RedisSemaphoreTimeoutError on timeout
try:
await sem.acquire(timeout=10)
try:
# do work
pass
finally:
await sem.release()
except RedisSemaphoreTimeoutError:
pass # handle timeout
# or use context manager (raises on timeout)
async with sem:
# do work
pass
asyncio.run(main())
N permits
Use count > 1 to allow N concurrent holders. count must be between 1 and 4096.
from redsync import SemaphoreInitStrategy
sem = await RedisSemaphore.create(r, "pool", count=5, semaphore_init_strategy=SemaphoreInitStrategy.LUA)
await sem.acquire()
# ...
await sem.release()
Init strategies
The semaphore uses a Redis list as a permit pool. The list must be created and filled with count elements before anyone can BLPOP. Two strategies are supported:
| Lua | SETNX | |
|---|---|---|
| Idea | Run a script that atomically ensures the list has N elements (if LLEN == 0 then RPUSH N times). |
Use a separate init key; the first process that wins SET NX creates the list and pushes N elements, then deletes the init key. |
| Pros | Single atomic op; no extra key; no crash race during init; idempotent. | No Lua; only basic commands; easy to debug in Redis. |
| Cons | Requires Lua (standard in Redis). | Extra key; two round-trips for the initializer (SETNX then RPUSH). |
Default is SemaphoreInitStrategy.LUA. Use SemaphoreInitStrategy.SETNX to avoid Lua.
Exceptions
RedisSemaphoreError- Base exceptionRedisSemaphoreTimeoutError–acquire()timed outRedisSemaphoreNotAcquiredError–release()called without acquiringRedisSemaphoreCountError–countnot in 1–4096
API Reference
RedisSemaphore
class RedisSemaphore:
@classmethod
async def create(cls, redis_client, name: str, *, count: int = 1,
semaphore_init_strategy: SemaphoreInitStrategy = SemaphoreInitStrategy.LUA,
key_prefix: str = "redis_semaphore") -> RedisSemaphore
async def acquire(self, timeout: float | None = None) -> None # None = block until available
async def release(self) -> None
async def __aenter__(self) -> RedisSemaphore
async def __aexit__(...) -> None
- name – Semaphore identifier (shared across processes).
- count – Number of permits (1–4096).
- timeout – For
acquire(): seconds to wait;Noneblocks indefinitely. RaisesRedisSemaphoreTimeoutErroron timeout.
Running tests
pytest
# or
uv run pytest
Set REDIS_URL if Redis is not on localhost:6379.
License
MIT License – see LICENSE.
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 redsync-1.0.0.tar.gz.
File metadata
- Download URL: redsync-1.0.0.tar.gz
- Upload date:
- Size: 60.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dfa1ef9462a88bbbe1d700eef6fa4b2f8f7e73d15b6128d89ee2e19eb760f2b7
|
|
| MD5 |
03679ef1a84fa3d746dac0cb24205b69
|
|
| BLAKE2b-256 |
140fa009ea4ef596841ce0300148701055a12715d63f5bc953859df0fdaf1d8a
|
Provenance
The following attestation bundles were made for redsync-1.0.0.tar.gz:
Publisher:
release.yml on martinmkhitaryan/redsync
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
redsync-1.0.0.tar.gz -
Subject digest:
dfa1ef9462a88bbbe1d700eef6fa4b2f8f7e73d15b6128d89ee2e19eb760f2b7 - Sigstore transparency entry: 985330253
- Sigstore integration time:
-
Permalink:
martinmkhitaryan/redsync@d9de17b5acffe6858da6d0f550b01622b9edb1a6 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/martinmkhitaryan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d9de17b5acffe6858da6d0f550b01622b9edb1a6 -
Trigger Event:
release
-
Statement type:
File details
Details for the file redsync-1.0.0-py3-none-any.whl.
File metadata
- Download URL: redsync-1.0.0-py3-none-any.whl
- Upload date:
- Size: 6.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
59e50e20f94493b82c0639a905647d930b71285b5f198ed565ec7d4c26a9bb8b
|
|
| MD5 |
b7798ed2608ee798bc02c902c45fb12e
|
|
| BLAKE2b-256 |
3aa80f8dd77a75f639ed1261eb5cd52622c90f936aebfd8e42cb150975f9b56b
|
Provenance
The following attestation bundles were made for redsync-1.0.0-py3-none-any.whl:
Publisher:
release.yml on martinmkhitaryan/redsync
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
redsync-1.0.0-py3-none-any.whl -
Subject digest:
59e50e20f94493b82c0639a905647d930b71285b5f198ed565ec7d4c26a9bb8b - Sigstore transparency entry: 985330258
- Sigstore integration time:
-
Permalink:
martinmkhitaryan/redsync@d9de17b5acffe6858da6d0f550b01622b9edb1a6 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/martinmkhitaryan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@d9de17b5acffe6858da6d0f550b01622b9edb1a6 -
Trigger Event:
release
-
Statement type: