You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
recussiveness of _thread.RLock doesn't work if lock acquired inside signal handler inside lock.acquire #144706
from_threadimportstart_new_thread,RLockfromselectimportselectfromosimportwritefromsignalimportsignal,SIGALRM,alarmlock=RLock()
defthreadfunc():
write(1,b"thread: trying to acquire lock\n")
lock.acquire()
write(1,b"thread: acquired lock and doing unrelated stuff for 3 seconds\n")
select((),(),(),3)
write(1,b"thread: trying to release lock\n")
lock.release()
write(1,b"thread: released lock\n")
defSIGALRMhandler(signum,frame):
write(1,b"SIGALRM: trying to acquire lock\n")
lock.acquire()
write(1,b"SIGALRM: acquired lock, returning\n")
signal(SIGALRM,SIGALRMhandler)
start_new_thread(threadfunc,())
alarm(2)
write(1,b"main: doing unrelated stuff for 1 second\n")
select((),(),(),1)
write(1,b"main: trying to acquire lock\n")
lock.acquire()#global scopewrite(1,b"main: acquired lock\nEND OF USELESS PROGRAM\n")
running:
$ python3 rlocktesting.pymain: doing unrelated stuff for 1 secondthread: trying to acquire lockthread: acquired lock and doing unrelated stuff for 3 secondsmain: trying to acquire lockSIGALRM: trying to acquire lockthread: trying to release lockthread: released lockSIGALRM: acquired lock, returning
it gets stuck forever, pressing ctrl+c:
^CTraceback (most recent call last): File "/tmp/rlocktesting.py", line 28, in <module> lock.acquire()#global scope ~~~~~~~~~~~~^^KeyboardInterrupt
I excpeted main thread to successfully acquire lock twice (first inside SIGALRMhandler, second continuing inside global scope)
changed the title [-]recussiveness of _thread.RLock doesn't work if lock aquired inside signal handler inside lock.acquire[/-][+]recussiveness of _thread.RLock doesn't work if lock acquired inside signal handler inside lock.acquire[/+]on Feb 11, 2026
You generally cannot acquire locks in signal handlers
the acquire locks in signal handlers part works, it's the recursiveness of RLock that doesn't work
if lock released inside SIGALRMhandler, continuing lock.acquire()#global scope doesn't get stuck
rlocktesting with release.py:
from_threadimportstart_new_thread,RLockfromselectimportselectfromosimportwritefromsignalimportsignal,SIGALRM,alarmlock=RLock()
defthreadfunc():
write(1,b"thread: trying to acquire lock\n")
lock.acquire()
write(1,b"thread: acquired lock and doing unrelated stuff for 3 seconds\n")
select((),(),(),3)
write(1,b"thread: trying to release lock\n")
lock.release()
write(1,b"thread: released lock\n")
defSIGALRMhandler(signum,frame):
write(1,b"SIGALRM: trying to acquire lock\n")
lock.acquire()
write(1,b"SIGALRM: acquired lock, releasing lock\n")
lock.release()
write(1,b"SIGALRM: released lock\n")
signal(SIGALRM,SIGALRMhandler)
start_new_thread(threadfunc,())
alarm(2)
write(1,b"main: doing unrelated stuff for 1 second\n")
select((),(),(),1)
write(1,b"main: trying to acquire lock\n")
lock.acquire()#global scopewrite(1,b"main: acquired lock\nEND OF USELESS PROGRAM\n")
running:
$ python3.13 'rlocktesting with release.py'main: doing unrelated stuff for 1 secondthread: trying to acquire lockthread: acquired lock and doing unrelated stuff for 3 secondsmain: trying to acquire lockSIGALRM: trying to acquire lockthread: trying to release lockthread: released lockSIGALRM: acquired lock, releasing lockSIGALRM: released lockmain: acquired lockEND OF USELESS PROGRAM
I was able to reproduce the behavior on my machine. The RLock does not grant re-entrance when acquired from the main thread as well as the signal handler concurrently. I find the assumption reasonable that one would be able to recurse into an RLock from the main-thread and a signal handler since the docs state:
Python signal handlers are always executed in the main Python thread of the main interpreter, even if the signal was received in another thread. This means that signals can’t be used as a means of inter-thread communication. You can use the synchronization primitives from the threading module instead.
Besides, only the main thread of the main interpreter is allowed to set a new signal handler.
CPython's recursive mutex is implemented using a non-recursive mutex that is shielded by a check for the current thread. With that in mind, here's the flow of calls:
The subthread acquires the lock, which forces the SIGALRM handler to wait when it attempts to acquire it. When there's no other thread holding the lock, _PyMutex_LockTimed takes a fast path that doesn't wait (and thus can't handle signals).
The main thread reaches the lock.acquire call and dives into _PyRecursiveMutex_LockTimed. It sees that the recursive mutex is not held by the main thread, so it begins waiting for the subthread to release it.
The main thread is then interrupted by the alarm signal, which triggers the signal handler.
The signal handler does the same dance; it goes to _PyRecursiveMutex_LockTimed, sees that the lock isn't owned by the main thread, and begins waiting. Now both the signal handler and the main program are waiting on the same non-recursive lock!
The subthread releases the lock, allowing the signal handler to take it and yield control to the main thread.
The main thread is still waiting on the non-recursive lock, because it still thinks that the lock is owned by the subthread
Deadlock!
This is just a quirk of signal handling. Please don't mix signals and locks.
Activity
[-]recussiveness of _thread.RLock doesn't work if lock aquired inside signal handler inside lock.acquire[/-][+]recussiveness of _thread.RLock doesn't work if lock acquired inside signal handler inside lock.acquire[/+]ZeroIntensity commentedon Feb 11, 2026
See #126434 for a similar issue:
You generally cannot acquire locks in signal handlers, regardless of language.
xBZZZZ commentedon Feb 11, 2026
the
acquire locks in signal handlerspart works, it's the recursiveness ofRLockthat doesn't workif lock released inside
SIGALRMhandler, continuinglock.acquire()#global scopedoesn't get stuckrlocktesting with release.py:running:
robsdedude commentedon Feb 11, 2026
I was able to reproduce the behavior on my machine. The RLock does not grant re-entrance when acquired from the main thread as well as the signal handler concurrently. I find the assumption reasonable that one would be able to recurse into an RLock from the main-thread and a signal handler since the docs state:
ZeroIntensity commentedon Feb 11, 2026
Locks in the
threadingmodule are interruptible, meaning they respond to signals (which is what allows you to interrupt them with CTRL+C):cpython/Modules/_threadmodule.c
Lines 1101 to 1103 in 3e0322f
(Notice the
_PY_LOCK_HANDLE_SIGNALSflag.)This is the cause of your deadlock. Let's take a look at
_PyRecursiveMutex_LockTimedfor some context:cpython/Python/lock.c
Lines 405 to 419 in 3e0322f
CPython's recursive mutex is implemented using a non-recursive mutex that is shielded by a check for the current thread. With that in mind, here's the flow of calls:
SIGALRMhandler to wait when it attempts to acquire it. When there's no other thread holding the lock,_PyMutex_LockTimedtakes a fast path that doesn't wait (and thus can't handle signals).lock.acquirecall and dives into_PyRecursiveMutex_LockTimed. It sees that the recursive mutex is not held by the main thread, so it begins waiting for the subthread to release it.alarmsignal, which triggers the signal handler._PyRecursiveMutex_LockTimed, sees that the lock isn't owned by the main thread, and begins waiting. Now both the signal handler and the main program are waiting on the same non-recursive lock!This is just a quirk of signal handling. Please don't mix signals and locks.
robsdedude commentedon Feb 11, 2026
Do you reckon it's worth adding some note like this to the docs?
ZeroIntensity commentedon Feb 11, 2026
Sure, if you can find a good place for it. There might be a note like this already, though.
xBZZZZ commentedon Feb 11, 2026
RLock) only mentions signals like this:RLockZeroIntensity commentedon Feb 12, 2026
Reopening as a docs issue
gh-144706: Warn against using synchronization primitives within signa…
pythongh-144706: Warn against using synchronization primitives within…
pythongh-144706: Warn against using synchronization primitives within…
[3.14] gh-144706: Warn against using synchronization primitives withi…
[3.13] gh-144706: Warn against using synchronization primitives withi…
pythongh-144706: Warn against using synchronization primitives within…
pythongh-144706: Warn against using synchronization primitives within…
pythongh-144706: Warn against using synchronization primitives within…