A reentrant lock lets one owning thread acquire the same lock in a nested method call.
Make this comfortable
Python RLock: permit nested ledger methods without dropping the outer lock
Operation contract
The ledger's reserve method acquires its lock, checks the input, and calls a locked balance method while still owning it. The nested acquisition succeeds, then reserve changes the balance. A rejected negative amount leaves the prior balance intact.
Failure boundary
RLock solves only same-thread reentry. It does not make a sequence of separate public method calls atomic, protect another process, or turn an external payment into a transaction. Every acquire must be matched by release; using with scopes makes the count visible.
Working program
from threading import RLock
class ReceiptBalance:
def __init__(self, opening_amount):
self._lock = RLock()
self._available = opening_amount
def available(self):
with self._lock:
return self._available
def reserve(self, amount):
with self._lock:
if type(amount) is not int or amount < 0 or amount > self.available():
raise ValueError("invalid reservation")
self._available -= amount
return self._available
balance = ReceiptBalance(73)
print("remaining", balance.reserve(47))
try:
balance.reserve(-1)
except ValueError:
print("negative_rejected", True)
print("unchanged", balance.available())Output
remaining 26
negative_rejected True
unchanged 26Costs and limits
RLock tracks the owning thread and recursion depth, adding bookkeeping to ordinary lock acquisition. Avoid nested public calls when a simpler private method can keep one lock scope.
Common Mistakes
- An ordinary non-reentrant Lock would block on this nested acquisition.
- RLock does not protect a multi-call client workflow by itself.
- Do not return from a manual acquire path without a matching release.
Connected lessons
python
rlock-nested-ledger
