gh-112175: Add eval_breaker to PyThreadState (#115194)

This change adds an `eval_breaker` field to `PyThreadState`. The primary
motivation is for performance in free-threaded builds: with thread-local eval
breakers, we can stop a specific thread (e.g., for an async exception) without
interrupting other threads.

The source of truth for the global instrumentation version is stored in the
`instrumentation_version` field in PyInterpreterState. Threads usually read the
version from their local `eval_breaker`, where it continues to be colocated
with the eval breaker bits.
This commit is contained in:
Brett Simmers 2024-02-20 06:57:48 -08:00 committed by GitHub
parent e71468ba4f
commit 0749244d13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 265 additions and 172 deletions

View file

@ -794,9 +794,7 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
Py_CLEAR(interp->audit_hooks);
// At this time, all the threads should be cleared so we don't need
// atomic operations for eval_breaker
interp->ceval.eval_breaker = 0;
interp->ceval.instrumentation_version = 0;
for (int i = 0; i < _PY_MONITORING_UNGROUPED_EVENTS; i++) {
interp->monitors.tools[i] = 0;
@ -1318,6 +1316,8 @@ init_threadstate(_PyThreadStateImpl *_tstate,
assert(interp != NULL);
tstate->interp = interp;
tstate->eval_breaker =
_Py_atomic_load_uintptr_relaxed(&interp->ceval.instrumentation_version);
// next/prev are set in add_threadstate().
assert(tstate->next == NULL);
@ -2021,8 +2021,7 @@ park_detached_threads(struct _stoptheworld_state *stw)
}
}
else if (state == _Py_THREAD_ATTACHED && t != stw->requester) {
// TODO: set this per-thread, rather than per-interpreter.
_Py_set_eval_breaker_bit(t->interp, _PY_EVAL_PLEASE_STOP_BIT, 1);
_Py_set_eval_breaker_bit(t, _PY_EVAL_PLEASE_STOP_BIT);
}
}
stw->thread_countdown -= num_parked;
@ -2186,19 +2185,18 @@ PyThreadState_SetAsyncExc(unsigned long id, PyObject *exc)
* deadlock, we need to release head_mutex before
* the decref.
*/
PyObject *old_exc = tstate->async_exc;
tstate->async_exc = Py_XNewRef(exc);
Py_XINCREF(exc);
PyObject *old_exc = _Py_atomic_exchange_ptr(&tstate->async_exc, exc);
HEAD_UNLOCK(runtime);
Py_XDECREF(old_exc);
_PyEval_SignalAsyncExc(tstate->interp);
_Py_set_eval_breaker_bit(tstate, _PY_ASYNC_EXCEPTION_BIT);
return 1;
}
HEAD_UNLOCK(runtime);
return 0;
}
//---------------------------------
// API for the current thread state
//---------------------------------