A flexible low-level tool to make synchronisation primitives in asyncio Python. As the name suggests, locks are granted strictly in the order requested: first-in-first-out; and are not reentrant.
pip install fifolock
import asyncio
from fifolock import FifoLock
class Mutex(asyncio.Future):
@staticmethod
def is_compatible(holds):
return not holds[Mutex]
lock = FifoLock()
async def access():
async with lock(Mutex):
# access resource
import asyncio
from fifolock import FifoLock
class Read(asyncio.Future):
@staticmethod
def is_compatible(holds):
return not holds[Write]
class Write(asyncio.Future):
@staticmethod
def is_compatible(holds):
return not holds[Read] and not holds[Write]
lock = FifoLock()
async def read():
async with lock(Read):
# shared access
async def write():
async with lock(Write):
# exclusive access
import asyncio
from fifolock import FifoLock
class SemaphoreBase(asyncio.Future):
@classmethod
def is_compatible(cls, holds):
return holds[cls] < cls.size
lock = FifoLock()
Semaphore = type('Semaphore', (SemaphoreBase, ), {'size': 3})
async def access():
async with lock(Semaphore):
# at most 3 concurrent accesses
python setup.py test
Each mode of the lock is a subclass of asyncio.Future
. This could be seen as a leak some of the internals of FifoLock
, but it allows for clear client and internal code.
-
Classes are hashable, so each can be a key in the
holds
dictionary passed to theis_compatible
method. This allows the compatibility conditions to be read clearly in the client code, and theholds
dictionary to be mutated clearly internally. -
An instance of it, created inside
FifoLock
, is both the object awaited upon, and stored in a deque with a way of accessing itsis_compatible
method. -
The fact it's a class and not an instance of a class also makes clear it is to store no state, merely configuration.
A downside is that for configurable modes, such as for a semaphore, the client must dynamically create a class: this is not a frequently-used pattern.
The fact that the lock is not reentrant is deliberate: the class of algorithms this is designed for does not require this. This would add unnecessary complexity, and presumably be slower.