A Redis/Valkey distributed semaphore built on top of Pottery.
pottery-semaphore provides distributed counting semaphores for coordinating access to shared resources across multiple processes and machines. It composes Pottery's battle-tested Redlock implementation with Redis atomic operations for permit tracking.
Install directly from GitHub:
pip install git+https://github.com/donicrosby/pottery-semaphore.gitOr clone and install locally:
git clone https://github.com/donicrosby/pottery-semaphore.git
cd pottery-semaphore
pip install .from redis import Redis
from pottery_semaphore import Semaphore
redis = Redis()
# Create a semaphore that allows 3 concurrent accesses
sem = Semaphore(value=3, key='my-resource', masters={redis})
# Use as a context manager
with sem:
# Critical section - at most 3 processes can be here simultaneously
do_work()import asyncio
from redis.asyncio import Redis
from pottery_semaphore import AIOSemaphore
async def main():
redis = Redis()
sem = AIOSemaphore(value=3, key='my-resource', masters={redis})
async with sem:
# Critical section with limited concurrency
await do_async_work()
asyncio.run(main())If you need more control than context managers provide:
from pottery_semaphore import Semaphore
sem = Semaphore(value=2, key='database-connections', masters={redis})
if sem.acquire():
try:
# Use the resource
pass
finally:
sem.release()Try to acquire without waiting:
# Returns immediately with True/False
if sem.acquire(blocking=False):
try:
# Got the permit
pass
finally:
sem.release()
else:
# No permits available, do something else
passWait up to a specified time for a permit:
# Wait up to 5 seconds for a permit
if sem.acquire(timeout=5):
try:
# Got the permit within 5 seconds
pass
finally:
sem.release()
else:
# Timed out waiting for permit
passBy default, semaphores are bounded - they raise BoundedSemaphoreError if you release more times than you acquire (similar to threading.BoundedSemaphore):
from pottery_semaphore import Semaphore, BoundedSemaphoreError
sem = Semaphore(value=1, key='bounded-example', masters={redis})
sem.release() # Raises BoundedSemaphoreError!For unbounded semaphores that allow extra releases:
sem = Semaphore(value=1, key='unbounded-example', masters={redis}, bounded=False)
sem.release() # OK - permits can exceed initial valueBoth Semaphore and AIOSemaphore accept the same parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
value |
int |
1 |
Initial number of permits |
key |
str |
"" |
Unique identifier for this semaphore |
masters |
Iterable[Redis] |
frozenset() |
Redis clients for distributed locking |
bounded |
bool |
True |
Raise error if released more than acquired |
raise_on_redis_errors |
bool |
False |
Raise when Redis errors prevent quorum |
# Synchronous
sem.value # Current available permits
sem.initial_value # Initial permit count
sem.locked() # True if no permits available
# Asynchronous
await sem.get_value()
await sem.get_initial_value()
await sem.locked()Synchronous distributed semaphore.
acquire(blocking=True, timeout=-1)→bool- Acquire a permitrelease(n=1)→None- Release permit(s)locked()→bool- Check if no permits availablevalue→int- Current available permits (property)initial_value→int- Initial permit count (property)
Asynchronous distributed semaphore with the same interface as Semaphore, but all methods are coroutines:
await acquire(blocking=True, timeout=-1)→boolawait release(n=1)→Noneawait locked()→boolawait get_value()→intawait get_initial_value()→int
SemaphoreError- Base exception for all semaphore errorsBoundedSemaphoreError- Raised when releasing a bounded semaphore would exceed its initial value
- Python 3.9+
- Redis or Valkey server
- pottery ≥ 3.0.1
pottery-semaphore combines several Redis primitives:
- Redlock (via Pottery) - Provides distributed mutual exclusion for atomic operations
- Counter - Tracks available permits
- Queue - Enables efficient blocking waits via Redis's
BLPOP
This composition ensures correct semaphore semantics across distributed systems while leveraging Pottery's robust Redlock implementation.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.