Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit 6da9ab0

Browse files
committed
pystate: abandon heaps before detaching PyThreadState
There was a period between detaching the PyThreadState on thread exit and abandoning the heap where a GC thread might see an inconsistent state of the heap. See #87
1 parent 62836fc commit 6da9ab0

File tree

1 file changed

+51
-44
lines changed

1 file changed

+51
-44
lines changed

Python/pystate.c

Lines changed: 51 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,8 +1203,35 @@ tstate_delete_common(PyThreadState *tstate,
12031203
Py_FatalError("PyThreadState_Delete: NULL interp");
12041204
}
12051205

1206-
_PyEventRC *join_event;
1206+
assert(is_current ? tstate->status == _Py_THREAD_ATTACHED
1207+
: tstate->status != _Py_THREAD_ATTACHED);
1208+
1209+
// Abandon heaps. After this point we must not allocate any Python objects.
1210+
if (tstate->heap_gc->gcstate == &tstate->interp->gc) {
1211+
tstate->heap_gc->gcstate = NULL;
1212+
}
1213+
if (tstate->heap_obj->thread_id == _Py_ThreadId() &&
1214+
_Py_IsMainInterpreter(tstate)) {
1215+
// NOTE: this may be called from a different thread. This can
1216+
// happend during shutdown of the interpreter or after forking.
1217+
// In these cases we don't delete the heap, because it's not
1218+
// safe to call that function from a different thread.
1219+
// FIXME(sgross): the interpeter check isn't great. Threads that
1220+
// are only in subinterpreters will leak. It's trying to avoid
1221+
// the problem where the main heap gets reset after a sub-interpreter
1222+
// that shares the main thread gets destroyed. That will set pages_free_direct
1223+
// to NULL and break mi_malloc calls.
1224+
mi_thread_done();
1225+
if (tstate->heap_backing) {
1226+
_mi_heap_done(tstate->heap_backing);
1227+
}
1228+
}
1229+
1230+
tstate->heap_backing = NULL;
1231+
tstate->heap_obj = NULL;
1232+
tstate->heap_gc = NULL;
12071233

1234+
_PyEventRC *join_event;
12081235
HEAD_LOCK(runtime);
12091236

12101237
/* Unlink thread state */
@@ -1217,15 +1244,16 @@ tstate_delete_common(PyThreadState *tstate,
12171244

12181245
interp->num_threads--;
12191246

1220-
/* The _PyEvent_Notify calls may block in acquiring parking lot locks.
1221-
* Set the status to detached so that it doesn't try to detach/reattach.
1222-
*/
1223-
tstate->status = _Py_THREAD_DETACHED;
1247+
// Set the status to detached so that _PyEvent_Notify does not try to
1248+
// detach/reattach when acquiring parking lot locks.
1249+
int32_t old_status = _Py_atomic_exchange_int32(&tstate->status, _Py_THREAD_DETACHED);
12241250

12251251
join_event = tstate->join_event;
12261252
tstate->join_event = NULL;
12271253

1228-
if (runtime->stop_the_world) {
1254+
if (runtime->stop_the_world &&
1255+
!_Py_CURRENTLY_FINALIZING(runtime, tstate) &&
1256+
old_status != _Py_THREAD_GC) {
12291257
struct _gc_runtime_state *gc = &tstate->interp->gc;
12301258
gc->gc_thread_countdown--;
12311259
assert(gc->gc_thread_countdown >= 0);
@@ -1238,63 +1266,42 @@ tstate_delete_common(PyThreadState *tstate,
12381266
runtime->ref_total += tstate->thread_ref_total;
12391267
tstate->thread_ref_total = 0;
12401268
#endif
1241-
12421269
HEAD_UNLOCK(runtime);
12431270

1244-
/* Notify threads waiting on Thread.join(). This should happen after the
1245-
* thread state is unlinked, but must happen before tstate->os is deleted.
1246-
*/
1271+
// Notify threads waiting on Thread.join(). This should happen after the
1272+
// thread state is unlinked, but must happen before parking lot is
1273+
// deinitialized.
12471274
if (join_event) {
12481275
_PyEvent_Notify(&join_event->event);
12491276
_PyEventRC_Decref(join_event);
12501277
}
12511278

1252-
if (tstate->heap_gc->gcstate == &tstate->interp->gc) {
1253-
tstate->heap_gc->gcstate = NULL;
1254-
}
1255-
1256-
if (tstate->heap_obj->thread_id == _Py_ThreadId() &&
1257-
_Py_IsMainInterpreter(tstate)) {
1258-
// NOTE: this may be called from a different thread. This can
1259-
// happend during shutdown of the interpreter or after forking.
1260-
// In these cases we don't delete the heap, because it's not
1261-
// safe to call that function from a different thread.
1262-
// FIXME(sgross): the interpeter check isn't great. Threads that
1263-
// are only in subinterpreters will leak. It's trying to avoid
1264-
// the problem where the main heap gets reset after a sub-interpreter
1265-
// that shares the main thread gets destroyed. That will set pages_free_direct
1266-
// to NULL and break mi_malloc calls.
1267-
mi_thread_done();
1268-
if (tstate->heap_backing) {
1269-
_mi_heap_done(tstate->heap_backing);
1270-
}
1271-
}
1272-
12731279
if (is_current) {
1274-
/* Current thread state can't be set to NULL until after join_event
1275-
* because it may be used by _PyEvent_Notify/_PyRawEvent_Notify calls.
1276-
* We also can't release it before mi_heap_done because then GC may proceed
1277-
* while abandoning is taking place. */
1280+
// TODO(sgross): I think this should be moved up between mi_heap_done() and
1281+
// unlinking thread state.
1282+
// We also can't release it before mi_heap_done because then GC may proceed
1283+
// while abandoning is taking place.
12781284
_PyRuntimeState_SetThreadState(runtime, NULL);
12791285

1280-
/* Drop the GIL if we hold it */
1286+
// Drop the GIL if we hold it
12811287
PyEval_ReleaseLock();
12821288
}
12831289

1284-
tstate->heap_backing = NULL;
1285-
tstate->heap_obj = NULL;
1286-
tstate->heap_gc = NULL;
1287-
1288-
_PyParkingLot_DeinitThread(tstate->waiter);
1289-
PyMem_RawFree(tstate);
1290-
12911290
if (gilstate->autoInterpreterState &&
12921291
PyThread_tss_get(&gilstate->autoTSSkey) == tstate)
12931292
{
12941293
PyThread_tss_set(&gilstate->autoTSSkey, NULL);
12951294
}
1296-
}
12971295

1296+
// TODO(sgross): what if this isn't the current thread? Might the owning
1297+
// thread be stuck in a parking lot lock? Will it have some sort of dead
1298+
// waiter? ruh roh.
1299+
if (is_current) {
1300+
_PyParkingLot_DeinitThread(tstate->waiter);
1301+
}
1302+
1303+
PyMem_RawFree(tstate);
1304+
}
12981305

12991306
static void
13001307
_PyThreadState_Delete(PyThreadState *tstate, int check_current)

0 commit comments

Comments
 (0)