@@ -278,6 +278,15 @@ static void recreate_gil(struct _gil_runtime_state *gil)
278
278
static void
279
279
drop_gil (struct _ceval_state * ceval , PyThreadState * tstate )
280
280
{
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
+
281
290
struct _gil_runtime_state * gil = ceval -> gil ;
282
291
if (!_Py_atomic_load_relaxed (& gil -> locked )) {
283
292
Py_FatalError ("drop_gil: GIL is not locked" );
@@ -298,7 +307,15 @@ drop_gil(struct _ceval_state *ceval, PyThreadState *tstate)
298
307
MUTEX_UNLOCK (gil -> mutex );
299
308
300
309
#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 )) {
302
319
MUTEX_LOCK (gil -> switch_mutex );
303
320
/* Not switched yet => wait */
304
321
if (((PyThreadState * )_Py_atomic_load_relaxed (& gil -> last_holder )) == tstate )
@@ -350,6 +367,9 @@ take_gil(PyThreadState *tstate)
350
367
int err = errno ;
351
368
352
369
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);
353
373
354
374
if (tstate_must_exit (tstate )) {
355
375
/* bpo-39877: If Py_Finalize() has been called and tstate is not the
@@ -625,10 +645,12 @@ _PyEval_AcquireLock(PyThreadState *tstate)
625
645
}
626
646
627
647
void
628
- _PyEval_ReleaseLock (PyThreadState * tstate )
648
+ _PyEval_ReleaseLock (PyInterpreterState * interp , PyThreadState * tstate )
629
649
{
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 ;
632
654
drop_gil (ceval , tstate );
633
655
}
634
656
0 commit comments