Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/alex-ber/AlexBerUtils/llms.txt

Use this file to discover all available pages before exploring further.

RLock is a reentrant lock that supports both synchronous (threading) and asynchronous (asyncio) contexts from a single object. It tracks ownership per-thread for sync code and per-task for async code, and uses FIFO queues so that waiting callers are woken in the order they arrived.
See the author’s article for an in-depth explanation of the design.

Initialisation

from alexber.utils.thread_locals import RLock

lock = RLock()
No arguments are required. The constructor creates the internal sync (threading.RLock) and async (asyncio.Lock) primitives along with their condition variables and FIFO queues.

Synchronous usage

Use RLock anywhere a standard threading.RLock would be used.
lock = RLock()

with lock:
    # critical section
    shared_resource.update()
Because the lock is reentrant, the same thread can acquire it multiple times without deadlocking:
lock = RLock()

def outer():
    with lock:      # acquires once
        inner()

def inner():
    with lock:      # re-enters safely; count becomes 2
        do_work()

outer()            # lock is fully released after outer() returns

Sync methods

acquire() -> bool

Blocks until the lock is available for the current thread. Returns True on success. If the calling thread already owns the lock, the reentrancy counter is incremented immediately.

release() -> bool

Decrements the reentrancy counter. When it reaches zero the lock is released and waiting threads are notified. Raises RuntimeError if the calling thread does not own the lock.

Asynchronous usage

Use async with or async_acquire / async_release inside async functions.
import asyncio
from alexber.utils.thread_locals import RLock

lock = RLock()

async def worker():
    async with lock:
        await shared_resource.async_update()

asyncio.run(worker())
Reentrancy works the same way in async: the same asyncio.Task can acquire the lock multiple times.
async def outer():
    async with lock:      # count = 1
        await inner()

async def inner():
    async with lock:      # count = 2 (same task)
        await do_async_work()

Async methods

async async_acquire() -> bool

Awaits until the lock is available for the current task. Returns True on success.

async async_release() -> bool

Decrements the async reentrancy counter. When it reaches zero, waiting tasks are notified via the condition variable. Raises RuntimeError if the current task does not own the lock.

Fairness

RLock uses collections.deque as a FIFO waiting queue for both sync and async contexts. New waiters are appended to the back of the queue; a waiter is only granted the lock when it is at the front. This prevents starvation under high contention.
Do not mix the sync and async APIs on the same RLock instance simultaneously. The sync lock and the async lock are independent primitives—holding the sync lock does not block async acquisition and vice versa.