Regarding interrupt safety of Python’s own Lock implementation, I found some interesting facts:
1. Lock().__enter__()
and Lock().__exit__()
seems to be internal C bindings that are immune to interrupts.
2. Wrappers around the lock (including Python’s own Condition
variables) are vulnerable to KeyboardInterrupt
.
Here is the code I used for test:
from threading import Lock, Condition
from sys import stdout, stderr
class WrappedLock:
def __init__(self):
self._lock = Lock()
def locked(self):
return self._lock.locked()
def __enter__(self):
return self._lock.__enter__()
def __exit__(self, *args):
# SIGINT here will cause unreleased lock (essentially a deadlock)
return self._lock.__exit__(*args)
lock1 = Lock()
lock2 = WrappedLock()
lock3 = Lock()
cond3 = Condition(lock3)
try:
while True:
with lock1, lock2, cond3:
# Ask dispatcher to send SIGINT
stdout.write("\n")
stdout.flush()
except KeyboardInterrupt:
pass
if lock1.locked():
stderr.write("lock1\n")
if lock2.locked():
stderr.write("lock2\n")
if lock3.locked():
stderr.write("lock3\n")
I used a custom script to run this test in batches. You can find the code in my Github Gist.
I ran only 10000 tests because it already made my MacBook’s fan roaring at me. Please feel free to run more tests on a more powerful machine. I am also curious if this result is reproducible on other OS/Architectures.
Here are the results:
# Python 3.12.5 on MacOS
Total 10000 tests
lock1 failed 0 times (0.00%)
lock2 failed 430 times (4.30%)
lock3 failed 514 times (5.14%)
This result shows that ContextManager
works (and ONLY works) with raw, unwrapped Python Lock
primitives to provide robust interrupt handling. I guess this will cause some very intricate confusions if someone assumes ContextManagers are always immune to interrupts.