Skip to content

Commit 811ca19

Browse files
committed
Switch to a single atomic field instead of using locks and events.
1 parent 321c783 commit 811ca19

5 files changed

Lines changed: 21 additions & 87 deletions

File tree

Include/internal/pycore_interp_structs.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,8 @@ struct _Py_unique_id_pool {
827827

828828
typedef _Py_CODEUNIT *(*_PyJitEntryFuncPtr)(struct _PyExecutorObject *exec, _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate);
829829

830+
#define _PyInterpreterGuard_GUARDS_NOT_ALLOWED UINTPTR_MAX
831+
830832
/* PyInterpreterState holds the global state for one of the runtime's
831833
interpreters. Typically the initial (main) interpreter is the only one.
832834
@@ -1053,11 +1055,10 @@ struct _is {
10531055
#endif
10541056
#endif
10551057

1056-
struct {
1057-
_PyRWMutex lock;
1058-
Py_ssize_t countdown;
1059-
PyEvent done;
1060-
} finalization_guards;
1058+
// The number of remaining finalization guards.
1059+
// If this is _PyInterpreterGuard_GUARDS_NOT_ALLOWED, then finalization
1060+
// guards can no longer be created.
1061+
uintptr_t finalization_guards;
10611062

10621063
/* the initial PyInterpreterState.threads.head */
10631064
_PyThreadStateImpl _initial_thread;

Include/internal/pycore_lock.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,6 @@ PyAPI_FUNC(int) _PyEvent_IsSet(PyEvent *evt);
8888
PyAPI_FUNC(void) _PyEvent_Notify(PyEvent *evt);
8989

9090

91-
// Reset an event. There must be no active waiters.
92-
void _PyEvent_Reset(PyEvent *evt);
93-
9491
// Wait for the event to be set. If the event is already set, then this returns
9592
// immediately.
9693
PyAPI_FUNC(void) PyEvent_Wait(PyEvent *evt);

Python/lock.c

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -313,15 +313,6 @@ _PyEvent_Notify(PyEvent *evt)
313313
}
314314
}
315315

316-
void
317-
_PyEvent_Reset(PyEvent *evt)
318-
{
319-
assert(evt != NULL);
320-
// It's not safe to reset an event with active waiters.
321-
assert(_Py_atomic_load_uint8(&evt->v) != _Py_HAS_PARKED);
322-
_Py_atomic_store_uint8(&evt->v, _Py_UNLOCKED);
323-
}
324-
325316
void
326317
PyEvent_Wait(PyEvent *evt)
327318
{

Python/pylifecycle.c

Lines changed: 5 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2314,37 +2314,6 @@ make_pre_finalization_calls(PyThreadState *tstate, int subinterpreters)
23142314
finalize_subinterpreters();
23152315
}
23162316

2317-
/* Wait on finalization guards.
2318-
*
2319-
* To avoid eating CPU cycles, we use an event to signal when we reach
2320-
* zero remaining guards. But, this isn't atomic! This event can be reset
2321-
* later if another thread creates a new finalization guard. The actual
2322-
* atomic check is made below, when we hold the finalization guard lock.
2323-
* Again, this is purely an optimization to avoid overloading the CPU.
2324-
*/
2325-
for (;;) {
2326-
if (_Py_atomic_load_ssize_relaxed(&interp->finalization_guards.countdown) == 0) {
2327-
break;
2328-
}
2329-
2330-
PyTime_t wait_ns = 1000 * 1000; // 1ms
2331-
if (PyEvent_WaitTimed(&interp->finalization_guards.done, wait_ns, /*detach=*/1)) {
2332-
break;
2333-
}
2334-
2335-
// For debugging purposes, we emit a fatal error if someone
2336-
// CTRL^C'ed the process.
2337-
if (PyErr_CheckSignals()) {
2338-
int fatal = PyErr_ExceptionMatches(PyExc_KeyboardInterrupt);
2339-
PyErr_FormatUnraisable("Exception ignored while waiting on finalization guards");
2340-
2341-
if (fatal) {
2342-
fputs("Interrupted while waiting on finalization guard", stderr);
2343-
exit(1);
2344-
}
2345-
}
2346-
}
2347-
23482317
/* Stop the world to prevent other threads from creating threads or
23492318
* atexit callbacks. On the default build, this is simply locked by
23502319
* the GIL. For pending calls, we acquire the dedicated mutex, because
@@ -2355,27 +2324,28 @@ make_pre_finalization_calls(PyThreadState *tstate, int subinterpreters)
23552324
// XXX Why does _PyThreadState_DeleteList() rely on all interpreters
23562325
// being stopped?
23572326
_PyEval_StopTheWorldAll(interp->runtime);
2358-
_PyRWMutex_Lock(&interp->finalization_guards.lock);
23592327
int has_subinterpreters = subinterpreters
23602328
? runtime_has_subinterpreters(interp->runtime)
23612329
: 0;
2330+
uintptr_t finalization_guards_expected = 0;
23622331
int should_continue = (interp_has_threads(interp)
23632332
|| interp_has_atexit_callbacks(interp)
23642333
|| interp_has_pending_calls(interp)
23652334
|| has_subinterpreters
2366-
|| _Py_atomic_load_ssize_acquire(&interp->finalization_guards.countdown) > 0);
2335+
|| _Py_atomic_compare_exchange_uintptr(&interp->finalization_guards,
2336+
&finalization_guards_expected,
2337+
_PyInterpreterGuard_GUARDS_NOT_ALLOWED) == 0);
23672338
if (!should_continue) {
23682339
break;
23692340
}
23702341
// Temporarily let other threads execute
23712342
_PyThreadState_Detach(tstate);
2372-
_PyRWMutex_Unlock(&interp->finalization_guards.lock);
23732343
_PyEval_StartTheWorldAll(interp->runtime);
23742344
PyMutex_Unlock(&interp->ceval.pending.mutex);
23752345
_PyThreadState_Attach(tstate);
23762346
}
23772347
assert(PyMutex_IsLocked(&interp->ceval.pending.mutex));
2378-
assert(_Py_atomic_load_ssize(&interp->finalization_guards.countdown) == 0);
2348+
assert(_Py_atomic_load_uintptr(&interp->finalization_guards) == _PyInterpreterGuard_GUARDS_NOT_ALLOWED);
23792349
ASSERT_WORLD_STOPPED(interp);
23802350
}
23812351

@@ -2434,7 +2404,6 @@ _Py_Finalize(_PyRuntimeState *runtime)
24342404
for (PyThreadState *p = list; p != NULL; p = p->next) {
24352405
_PyThreadState_SetShuttingDown(p);
24362406
}
2437-
_PyRWMutex_Unlock(&tstate->interp->finalization_guards.lock);
24382407
_PyEval_StartTheWorldAll(runtime);
24392408
PyMutex_Unlock(&tstate->interp->ceval.pending.mutex);
24402409

@@ -2817,7 +2786,6 @@ Py_EndInterpreter(PyThreadState *tstate)
28172786
_PyThreadState_SetShuttingDown(p);
28182787
}
28192788

2820-
_PyRWMutex_Unlock(&interp->finalization_guards.lock);
28212789
_PyEval_StartTheWorldAll(interp->runtime);
28222790
PyMutex_Unlock(&interp->ceval.pending.mutex);
28232791
_PyThreadState_DeleteList(list, /*is_after_fork=*/0);

Python/pystate.c

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3322,7 +3322,7 @@ Py_ssize_t
33223322
_PyInterpreterState_GuardCountdown(PyInterpreterState *interp)
33233323
{
33243324
assert(interp != NULL);
3325-
Py_ssize_t count = _Py_atomic_load_ssize_relaxed(&interp->finalization_guards.countdown);
3325+
Py_ssize_t count = _Py_atomic_load_uintptr_relaxed(&interp->finalization_guards);
33263326
assert(count >= 0);
33273327
return count;
33283328
}
@@ -3339,32 +3339,14 @@ static int
33393339
try_acquire_interp_guard(PyInterpreterState *interp, PyInterpreterGuard *guard)
33403340
{
33413341
assert(interp != NULL);
3342-
_PyRWMutex_RLock(&interp->finalization_guards.lock);
33433342

3344-
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
3345-
_PyRWMutex_RUnlock(&interp->finalization_guards.lock);
3346-
assert(_Py_atomic_load_ssize_relaxed(&interp->finalization_guards.countdown) == 0);
3347-
return -1;
3348-
}
3349-
3350-
Py_ssize_t old_value = _Py_atomic_add_ssize(&interp->finalization_guards.countdown, 1);
3351-
if (old_value == 0) {
3352-
// We first have to notify the finalization thread if it's waiting on us, but
3353-
// it will get trapped waiting on the RW lock, so we don't have to worry about
3354-
// another active waiter while we reset the event.
3355-
_PyEvent_Notify(&interp->finalization_guards.done);
3356-
// Now, when the finalization thread goes to check the countdown
3357-
// after we release the lock, it will see that the countdown is
3358-
// non-zero and begin waiting again (hence why we need to reset the
3359-
// event).
3360-
// It is worth noting that this event is intentionally a non-atomic check
3361-
// for remaining finalization guards (that is, even if the event is set,
3362-
// it is not necessarily guaranteed that countdown is zero). We use it
3363-
// to simply prevent the finalization from constantly spinning and
3364-
// atomically reading the countdown.
3365-
_PyEvent_Reset(&interp->finalization_guards.done);
3366-
}
3367-
_PyRWMutex_RUnlock(&interp->finalization_guards.lock);
3343+
uintptr_t expected;
3344+
do {
3345+
expected = _Py_atomic_load_uintptr(&interp->finalization_guards);
3346+
if (expected == _PyInterpreterGuard_GUARDS_NOT_ALLOWED) {
3347+
return -1;
3348+
}
3349+
} while (_Py_atomic_compare_exchange_uintptr(&interp->finalization_guards, &expected, expected + 1) == 0);
33683350

33693351
guard->interp = interp;
33703352
return 0;
@@ -3398,14 +3380,9 @@ PyInterpreterGuard_Close(PyInterpreterGuard *guard)
33983380
PyInterpreterState *interp = guard->interp;
33993381
assert(interp != NULL);
34003382

3401-
_PyRWMutex_RLock(&interp->finalization_guards.lock);
3402-
Py_ssize_t old = _Py_atomic_add_ssize(&interp->finalization_guards.countdown, -1);
3403-
if (old == 1) {
3404-
_PyEvent_Notify(&interp->finalization_guards.done);
3405-
}
3406-
_PyRWMutex_RUnlock(&interp->finalization_guards.lock);
3383+
assert(_Py_atomic_load_uintptr(&interp->finalization_guards) != _PyInterpreterGuard_GUARDS_NOT_ALLOWED);
3384+
_Py_atomic_add_uintptr(&interp->finalization_guards, -1);
34073385

3408-
assert(old > 0);
34093386
PyMem_RawFree(guard);
34103387
}
34113388

0 commit comments

Comments
 (0)