@@ -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
@@ -630,10 +650,12 @@ _PyEval_AcquireLock(PyThreadState *tstate)
630
650
}
631
651
632
652
void
633
- _PyEval_ReleaseLock (PyThreadState * tstate )
653
+ _PyEval_ReleaseLock (PyInterpreterState * interp , PyThreadState * tstate )
634
654
{
635
- _Py_EnsureTstateNotNULL (tstate );
636
- struct _ceval_state * ceval = & tstate -> interp -> ceval ;
655
+ /* If tstate is NULL then we do not expect the current thread
656
+ to acquire the GIL ever again. */
657
+ assert (tstate == NULL || tstate -> interp == interp );
658
+ struct _ceval_state * ceval = & interp -> ceval ;
637
659
drop_gil (ceval , tstate );
638
660
}
639
661
0 commit comments