Repository navigation
Non-blocking _PyMutex_LockTimed spins and may fail unnecessarily in no-GIL builds #135871
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Jun 24, 2025 - added 4 commits that reference this issue
on Jun 24, 2025 - addedinterpreter-core(Objects, Python, Grammar, and Parser dirs)(Objects, Python, Grammar, and Parser dirs)
on Jun 24, 2025 - added3.13only security fixesonly security fixes3.14bugs and security fixesbugs and security fixes3.15bugs and security fixesbugs and security fixes
on Jun 24, 2025 The issue and proposed fix make sense to me, but I'm also a little suspicious. I would think that this
continueneeds to reload the lock bits, because otherwise we're going to spin without reloading, as stated in the issue. But maybe this was omitted for contention reasons? I can't find anything in the PR about it.Sam based
PyMutexoff of WebKit's locking implementation. Reading the article, it seems that they do include an atomic load for each spin, but it was also written several years beforePyMutexwent in. There's a chance they removed that atomic for performance's sake in-between 2016 and 2023. I'll wait for his input.Reacted by Dupratcc @colesbury @mpage
I tracked down WebKit's locking implementation, and it seems they do have the atomic load for each iteration: https://github.com/WebKit/WebKit/blob/7ebf83c48305f8aac9f37af71af77dd66b758811/Source/WTF/wtf/LockAlgorithmInlines.h#L48
We found that it hurts performance for us, though. Is everyone good with closing this?
No, we should fix it, but carefully in a way that doesn't hurt performance.
Ok. Perhaps only reloading every few spins or so?
I'm interested into taking this issue if no one objects, and I've started to look into it.
Go ahead @dpdani!
Reacted by Daniele Parmeggiani- added a commit that references this issue
on Mar 23, 2026 Fixed by #146064
Bug report
Bug description:
Non-blocking
_PyMutex_LockTimedspins and may fail unnecessarily (no-GIL build)Branch:
main(commit2793b68f758c10fb63b264787f10d46a71fc8086)Build: configured with
--disable-gil(MAX_SPIN_COUNT > 0)OS: all
Summary
_PyMutex_LockTimed()is supposed to return immediately when called withtimeout == 0(non-blocking). In a no-GIL build it:MAX_SPIN_COUNTyields before returning;PY_LOCK_FAILUREeven though the lock was released during that spin.Timed/blocking calls (
timeout > 0) also waste CPU because the spin loopnever reloads the lock word, so they can’t notice an unlock until after the
maximum spin count.
The bug is invisible in GIL builds because
MAX_SPIN_COUNTis 0.Root cause
else ifguard is skipped, so anon-blocking call drops into the spin loop.
vis never reloaded, so the thread can’t see that thelock became free. After
MAX_SPIN_COUNTiterations it falls through tothe same
timeout == 0guard and fails spuriously.Proposed fix
Result
failure if it loses – no spinning, no parking.
lock word each iteration, so they acquire promptly once the lock is free.
CPython versions tested on:
CPython main branch
Operating systems tested on:
macOS
Linked PRs
_PyMutex_LockTimedwith zero timeout (gh-135872) #135946_PyMutex_LockTimedwith zero timeout (gh-135872) #135947PyMutex_LockTimed#146064