@@ -278,6 +278,15 @@ static void recreate_gil(struct _gil_runtime_state *gil)
278278static void
279279drop_gil (struct _ceval_state * ceval , PyThreadState * tstate )
280280{
281+ /* If tstate is NULL, the caller is indicating that we're releasing
282+ the GIL for the last time in this thread. This is particularly
283+ relevant when the current thread state is finalizing or its
284+ interpreter is finalizing (either may be in an inconsistent
285+ state). In that case the current thread will definitely
286+ never try to acquire the GIL again. */
287+ // XXX It may be more correct to check tstate->_status.finalizing.
288+ // XXX assert(tstate == NULL || !tstate->_status.cleared);
289+
281290 struct _gil_runtime_state * gil = ceval -> gil ;
282291 if (!_Py_atomic_load_relaxed (& gil -> locked )) {
283292 Py_FatalError ("drop_gil: GIL is not locked" );
@@ -298,7 +307,15 @@ drop_gil(struct _ceval_state *ceval, PyThreadState *tstate)
298307 MUTEX_UNLOCK (gil -> mutex );
299308
300309#ifdef FORCE_SWITCHING
301- if (_Py_atomic_load_relaxed (& ceval -> gil_drop_request ) && tstate != NULL ) {
310+ /* We check tstate first in case we might be releasing the GIL for
311+ the last time in this thread. In that case there's a possible
312+ race with tstate->interp getting deleted after gil->mutex is
313+ unlocked and before the following code runs, leading to a crash.
314+ We can use (tstate == NULL) to indicate the thread is done with
315+ the GIL, and that's the only time we might delete the
316+ interpreter, so checking tstate first prevents the crash.
317+ See https://github.com/python/cpython/issues/104341. */
318+ if (tstate != NULL && _Py_atomic_load_relaxed (& ceval -> gil_drop_request )) {
302319 MUTEX_LOCK (gil -> switch_mutex );
303320 /* Not switched yet => wait */
304321 if (((PyThreadState * )_Py_atomic_load_relaxed (& gil -> last_holder )) == tstate )
@@ -350,6 +367,9 @@ take_gil(PyThreadState *tstate)
350367 int err = errno ;
351368
352369 assert (tstate != NULL );
370+ /* We shouldn't be using a thread state that isn't viable any more. */
371+ // XXX It may be more correct to check tstate->_status.finalizing.
372+ // XXX assert(!tstate->_status.cleared);
353373
354374 if (tstate_must_exit (tstate )) {
355375 /* bpo-39877: If Py_Finalize() has been called and tstate is not the
@@ -625,10 +645,12 @@ _PyEval_AcquireLock(PyThreadState *tstate)
625645}
626646
627647void
628- _PyEval_ReleaseLock (PyThreadState * tstate )
648+ _PyEval_ReleaseLock (PyInterpreterState * interp , PyThreadState * tstate )
629649{
630- _Py_EnsureTstateNotNULL (tstate );
631- struct _ceval_state * ceval = & tstate -> interp -> ceval ;
650+ /* If tstate is NULL then we do not expect the current thread
651+ to acquire the GIL ever again. */
652+ assert (tstate == NULL || tstate -> interp == interp );
653+ struct _ceval_state * ceval = & interp -> ceval ;
632654 drop_gil (ceval , tstate );
633655}
634656
0 commit comments