Skip to content

Commit 23a84ab

Browse files
committed
Use an event for waiting on guards.
1 parent 19dfaca commit 23a84ab

File tree

4 files changed

+32
-9
lines changed

4 files changed

+32
-9
lines changed

Include/cpython/pystate.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,4 +337,4 @@ PyAPI_FUNC(void) _PyInterpreterState_SetEvalFrameAllowSpecialization(
337337
PyInterpreterState *interp,
338338
int allow_specialization);
339339
PyAPI_FUNC(int) _PyInterpreterState_IsSpecializationEnabled(
340-
PyInterpreterState *interp);
340+
PyInterpreterState *interp);

Include/internal/pycore_interp_structs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,7 @@ struct _is {
10531053
struct {
10541054
_PyRWMutex lock;
10551055
Py_ssize_t countdown;
1056+
PyEvent done;
10561057
} finalization_guards;
10571058

10581059
/* the initial PyInterpreterState.threads.head */

Python/pylifecycle.c

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2309,16 +2309,25 @@ make_pre_finalization_calls(PyThreadState *tstate, int subinterpreters)
23092309

23102310
if (subinterpreters) {
23112311
/* Clean up any lingering subinterpreters.
2312-
2313-
Two preconditions need to be met here:
2314-
2315-
- This has to happen before _PyRuntimeState_SetFinalizing is
2316-
called, or else threads might get prematurely blocked.
2317-
- The world must not be stopped, as finalizers can run.
2318-
*/
2312+
* Two preconditions need to be met here:
2313+
* 1. This has to happen before _PyRuntimeState_SetFinalizing is
2314+
* called, or else threads might get prematurely blocked.
2315+
* 2. The world must not be stopped, as finalizers can run.
2316+
*/
23192317
finalize_subinterpreters();
23202318
}
23212319

2320+
/* Wait on finalization guards.
2321+
*
2322+
* To avoid eating CPU cycles, we use an event to signal when we reach
2323+
* zero remaining guards. But, this isn't atomic! This event can be reset
2324+
* later if another thread creates a new finalization guard. The actual
2325+
* atomic check is made below, when we hold the finalization guard lock.
2326+
* Again, this is purely an optimization to avoid overloading the CPU.
2327+
*/
2328+
if (_Py_atomic_load_ssize_relaxed(&interp->finalization_guards.countdown) > 0) {
2329+
PyEvent_Wait(&interp->finalization_guards.done);
2330+
}
23222331

23232332
/* Stop the world to prevent other threads from creating threads or
23242333
* atexit callbacks. On the default build, this is simply locked by

Python/pystate.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3356,7 +3356,17 @@ try_acquire_interp_guard(PyInterpreterState *interp, PyInterpreterGuard *guard)
33563356
return -1;
33573357
}
33583358

3359-
_Py_atomic_add_ssize(&interp->finalization_guards.countdown, 1);
3359+
Py_ssize_t old_value = _Py_atomic_add_ssize(&interp->finalization_guards.countdown, 1);
3360+
if (old_value == 0) {
3361+
// Reset the event.
3362+
// We first have to notify the finalization thread if it's waiting on us, but
3363+
// it will get trapped waiting on the RW lock. When it goes to check
3364+
// again after we release the lock, it will see that the countdown is
3365+
// non-zero and begin waiting again (hence why we need to reset the
3366+
// event).
3367+
_PyEvent_Notify(&interp->finalization_guards.done);
3368+
memset(&interp->finalization_guards.done, 0, sizeof(PyEvent));
3369+
}
33603370
_PyRWMutex_RUnlock(&interp->finalization_guards.lock);
33613371

33623372
guard->interp = interp;
@@ -3393,6 +3403,9 @@ PyInterpreterGuard_Close(PyInterpreterGuard *guard)
33933403

33943404
_PyRWMutex_RLock(&interp->finalization_guards.lock);
33953405
Py_ssize_t old = _Py_atomic_add_ssize(&interp->finalization_guards.countdown, -1);
3406+
if (old == 1) {
3407+
_PyEvent_Notify(&interp->finalization_guards.done);
3408+
}
33963409
_PyRWMutex_RUnlock(&interp->finalization_guards.lock);
33973410

33983411
assert(old > 0);

0 commit comments

Comments
 (0)