Repository navigation
Crash in PyThreadState_DeleteCurrent: drop_gil: GIL is not locked (free-threading) #118727
Description
Activity
- addedtestsTests in the Lib/test dirTests in the Lib/test dirtype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
on May 7, 2024 I think the race here isn't specific to the thread deletion sequence; I forgot that
drop_gil()is called by detached threads and sogil->enabledcan change during it (liketake_gil(), which has protection against this). Fix shouldn't be too hard.edit: I was wrong; the thread is still attached during
drop_gil()and this is specific to thread deletion. The issue is that tstate_delete_common() decrements the countdown for any pending stop-the-world requests. If there is a thread waiting to enable the GIL, there's a decent chance that this will activate the stw which will immediately enable the GIL.Reacted by Sam GrossReacted by stonebigFWIW, this issue (and the PR) reinforce a worry I have about the complexity of Python's runtime state relative to threads.
At a conceptual level the runtime has greater-than-one pieces of per-thread data for which the possible value classes/groupings (in combination) should map explicitly onto distinct operational states (while disallowing some value class combinations). We have meaningful gaps in identifying such a mapping and enforcing such constraints, which makes it hard to be confident about correctness (in such a critical part of the runtime), as well as making it easier to introduce unnecessary complexity. This observation is supported by the variety of defects we've seen relative to the GIL over the years, including recently, including this issue.
I recognize all to personally how hard a hard problem this is, and it's not new with the free-threading work, but it's something we should be extra thoughtful about sooner to avoid additional headache later.
FWIW, at one point I tried to tame this situation somewhat and codify it by adding
PyThreadState._status, along with a bunch of asserts. However, that approach can only take us so far and, even then, I don't feel like I was rigorous enough nor was I able to constrain the states as much as I wanted (e.g. I had to comment out various asserts). As part of the free-threading work, I've seen a number of improvements in support of mapping data to state and constraining that programmatically. I've also noticed new gaps.In conclusion, we've reached a point in complexity (which free-threading generally amplifies meaningfully) that it may be worth taking some time to more thoroughly identify and validate the (effective) state machine for the Python runtime relative to threads. Otherwise I fear we'll regularly run into bugs like this indefinitely.
Reacted by Brett Simmersit may be worth taking some time to more thoroughly identify and validate the (effective) state machine for the Python runtime relative to threads.
I agree. One that I'd especially like to address (it's a minor one but I run into it frequently) is that I believe this store is redundant, based on my understanding of how
last_holderis managed. It's been around for over 13 years, so it's plausible that things have changed enough in the meantime.Reacted by Eric Snow- added a commit that references this issue
on May 23, 2024 - added a commit that references this issue
on May 23, 2024
Crash report
cpython/Python/ceval_gil.c
Lines 232 to 239 in b9caa09
Found by running
./python Lib/test/test_importlib/partial/pool_in_threads.pyNote that in the coredump
gil->enabledis0, so it looks like the gil was transiently enabled by a different thread during the call to_PyThreadState_DeleteCurrent.cc @swtaarrs
Linked PRs
drop_gil()unless the current thread holds it #118745drop_gil()unless the current thread holds it (GH-118745) #119474