Skip to content

Fix free-threaded deadlock when switching greenlets holding a critical section#519

Open
ddorian wants to merge 1 commit into
python-greenlet:masterfrom
ddorian:freethread-switch-critical-section
Open

Fix free-threaded deadlock when switching greenlets holding a critical section#519
ddorian wants to merge 1 commit into
python-greenlet:masterfrom
ddorian:freethread-switch-critical-section

Conversation

@ddorian

@ddorian ddorian commented Jul 3, 2026

Copy link
Copy Markdown

fixes #518

…l section

On free-threaded builds the runtime guards objects with per-object locks taken
through Py_BEGIN_CRITICAL_SECTION and tracked, per thread, in
tstate->critical_section. Those sections are scoped to the C stack. A greenlet
switch swaps C stacks but previously only moved the tstate->critical_section
head between greenlets -- it never released the underlying PyMutexes.

So when a switch happened while a section was held (for example inside
asyncio's Task.__step, which holds one on the running task for the whole step),
the locks stayed held on the greenlet we left. The greenlet we switched to then
blocked forever trying to take one of those same locks. This is what hung
Playwright's synchronous API under free-threading; it reproduces with nothing
but asyncio and greenlet (see freethread_switch_deadlock.py).

Suspend a thread's critical sections in PythonState::operator<< and resume them
in operator>>, the same way the interpreter does across a thread detach and
reattach. A brand-new greenlet starts with an empty chain, so this is a no-op
until something is actually held.
@ddorian

ddorian commented Jul 3, 2026

Copy link
Copy Markdown
Author

I think the flaky test is a flake, correct? https://github.com/python-greenlet/greenlet/actions/runs/28684605712/job/85074798831?pr=519

I can't rerun the tests though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

greenlet deadlocks on free-threaded CPython when a switch happens while a PyCriticalSection is held

1 participant