AioLogic
AioLogic is a GIL-powered locking library for Python (current version 0.16.0) that provides synchronization primitives which are both async-aware and thread-aware. It addresses common challenges in concurrent programming by enabling seamless interaction between asynchronous code (within and across multiple threads/event loops) and synchronous code, and between different synchronous threads, offering a unified API for various concurrency models. The library is actively maintained with a focus on performance and broad compatibility.
Common errors
-
RuntimeError: Cannot run asyncio.run() in a running event loop
cause Attempting to use `asyncio.Lock` or other `asyncio` primitives to synchronize across multiple `asyncio.run()` calls invoked from different threads, or between different event loops.fixUse `aiologic.Lock` instead. `aiologic` primitives are designed to be both async-aware and thread-aware, allowing synchronization across different event loops in separate threads. -
Application deadlocks or unresponsive tasks when trying to acquire a lock in a mixed async/sync environment.
cause Using `threading.Lock` in an asynchronous context or to synchronize tasks running in different `asyncio` event loops. `threading.Lock` is blocking and not aware of event loop scheduling, leading to deadlocks.fixReplace `threading.Lock` with `aiologic.Lock`. `aiologic.Lock` provides a non-blocking, async-aware, and thread-safe mechanism for synchronization. -
TypeError: object <lock_type> is not awaitable
cause Attempting to `await` a synchronous lock primitive (e.g., from `threading` module) or using an asynchronous primitive (e.g., from `asyncio`) in a synchronous context without proper handling.fixEnsure you are using the correct `aiologic` primitive (`aiologic.Lock`, `aiologic.Condition`, etc.) for your context. `aiologic` primitives expose both synchronous (e.g., `acquire()`, `release()`) and asynchronous (e.g., `async_acquire()`, `async_release()`, or `async with`) interfaces that adapt to the environment.
Warnings
- breaking Version 0.13.0 introduced significant internal source code refactoring and type annotations via stubs. Objects pickled with previous versions of `aiologic` might become unusable.
- gotcha Unlike `asyncio.Lock` or `threading.Lock`, `aiologic.Lock` is designed to work across different asyncio event loops running in separate threads, and to synchronize between async and sync code. Directly using `asyncio.Lock` in such cross-thread/cross-event-loop scenarios will result in a `RuntimeError`, and `threading.Lock` will lead to deadlocks by blocking event loops.
- deprecated The `aiologic.lowlevel.shield()` function was replaced by `aiologic.lowlevel.repeat_if_cancelled()` due to limitations with `anyio.CancelScope` not reliably shielding calls from external cancellations.
- gotcha In version 0.14.0, the mechanism for detecting support for underlying concurrency libraries (like `eventlet`, `gevent`, `asyncio`, `trio`, `curio`, `anyio`) changed from post-first-use to post-import hooks. This means library support is activated when the corresponding library is imported, which might alter behavior in interactive or dynamically loaded scenarios compared to previous versions.
Install
-
pip install aiologic
Imports
- Lock
from aiologic import Lock
- Condition
from aiologic import Condition
- synchronized
from aiologic import synchronized
Quickstart
import asyncio
from threading import Thread
from aiologic import Lock
lock = Lock()
async def func(i: int, j: int) -> None:
print(f"thread={i} task={j} start")
async with lock:
await asyncio.sleep(0.1) # Simulate some async work
print(f"thread={i} task={j} end")
async def main(i: int) -> None:
await asyncio.gather(func(i, 0), func(i, 1))
# Run two asyncio event loops in separate threads, sharing one aiologic.Lock
thread0 = Thread(target=asyncio.run, args=[main(0)])
thread1 = Thread(target=asyncio.run, args=[main(1)])
thread0.start()
thread1.start()
thread0.join()
thread1.join()
print("All threads and tasks completed.")