I hit this when trying free-threading with playwright in microsoft/playwright-python#3123.
This happens also with gil=on.
Is this valid? Can I try to make a pull request?
❯ uv run --no-project --python 3.14.6t repro_min.py
python 3.14.6 gil_enabled=False
Fatal Python error: PyMutex_Unlock: unlocking mutex that is not locked
Python runtime state: initialized
Stack (most recent call first):
File "/home/guru/Desktop/repro_min.py", line 39 in g2_body
Extension modules: greenlet._greenlet (total: 1)
script:
# /// script
# requires-python = "==3.14.6"
# dependencies = ["greenlet==3.5.2"]
# ///
# Run on free-threaded CPython: uv run --no-project --python 3.14t repro_min.py
#
# greenlet never saves/restores CPython's per-thread critical-section pointer
# (tstate->critical_section) across a stack switch. Two greenlets that each
# switch *while holding a list.sort() critical section* interleave non-nested,
# corrupting the chain; the next blocking open() then aborts in
# _PyCriticalSection_SuspendAll with "PyMutex_Unlock: unlocking mutex that is
# not locked".
import sys
import greenlet
print(f"python {sys.version.split()[0]} gil_enabled={sys._is_gil_enabled()}", flush=True)
main = greenlet.getcurrent()
def k1(x):
g2.switch() # into G2 while holding G1's sort critical section
return x
def k2(x):
g1.switch() # back into G1 while holding G2's sort critical section
return x
def g1_body():
sorted([0], key=k1)
g2.switch()
def g2_body():
sorted([0], key=k2)
open("/etc/hostname").read() # SuspendAll over a stale, unlocked mutex -> fatal
g1 = greenlet.greenlet(g1_body)
g2 = greenlet.greenlet(g2_body)
g1.switch()
print("DONE no crash")
I hit this when trying free-threading with playwright in microsoft/playwright-python#3123.
This happens also with gil=on.
Is this valid? Can I try to make a pull request?
script: