gh-127791: Fix, document, and test PyUnstable_AtExit (#127793)

This commit is contained in:
Peter Bierma 2024-12-11 06:14:04 -05:00 committed by GitHub
parent 2cdeb61b57
commit d5d84c3f13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 71 additions and 39 deletions

View File

@ -567,6 +567,15 @@ Initializing and finalizing the interpreter
customized Python that always runs in isolated mode using
:c:func:`Py_RunMain`.
.. c:function:: int PyUnstable_AtExit(PyInterpreterState *interp, void (*func)(void *), void *data)
Register an :mod:`atexit` callback for the target interpreter *interp*.
This is similar to :c:func:`Py_AtExit`, but takes an explicit interpreter and
data pointer for the callback.
The :term:`GIL` must be held for *interp*.
.. versionadded:: 3.13
Process-wide parameters
=======================

View File

@ -426,3 +426,7 @@ Process Control
function registered last is called first. Each cleanup function will be called
at most once. Since Python's internal finalization will have completed before
the cleanup function, no Python APIs should be called by *func*.
.. seealso::
:c:func:`PyUnstable_AtExit` for passing a ``void *data`` argument.

View File

@ -44,7 +44,6 @@ typedef struct {
struct atexit_state {
atexit_callback *ll_callbacks;
atexit_callback *last_ll_callback;
// XXX The rest of the state could be moved to the atexit module state
// and a low-level callback added for it during module exec.

View File

@ -0,0 +1,2 @@
Fix loss of callbacks after more than one call to
:c:func:`PyUnstable_AtExit`.

View File

@ -3353,6 +3353,53 @@ type_freeze(PyObject *module, PyObject *args)
Py_RETURN_NONE;
}
struct atexit_data {
int called;
PyThreadState *tstate;
PyInterpreterState *interp;
};
static void
atexit_callback(void *data)
{
struct atexit_data *at_data = (struct atexit_data *)data;
// Ensure that the callback is from the same interpreter
assert(PyThreadState_Get() == at_data->tstate);
assert(PyInterpreterState_Get() == at_data->interp);
++at_data->called;
}
static PyObject *
test_atexit(PyObject *self, PyObject *Py_UNUSED(args))
{
PyThreadState *oldts = PyThreadState_Swap(NULL);
PyThreadState *tstate = Py_NewInterpreter();
struct atexit_data data = {0};
data.tstate = PyThreadState_Get();
data.interp = PyInterpreterState_Get();
int amount = 10;
for (int i = 0; i < amount; ++i)
{
int res = PyUnstable_AtExit(tstate->interp, atexit_callback, (void *)&data);
if (res < 0) {
Py_EndInterpreter(tstate);
PyThreadState_Swap(oldts);
PyErr_SetString(PyExc_RuntimeError, "atexit callback failed");
return NULL;
}
}
Py_EndInterpreter(tstate);
PyThreadState_Swap(oldts);
if (data.called != amount) {
PyErr_SetString(PyExc_RuntimeError, "atexit callback not called");
return NULL;
}
Py_RETURN_NONE;
}
static PyMethodDef TestMethods[] = {
{"set_errno", set_errno, METH_VARARGS},
@ -3495,6 +3542,7 @@ static PyMethodDef TestMethods[] = {
{"test_critical_sections", test_critical_sections, METH_NOARGS},
{"finalize_thread_hang", finalize_thread_hang, METH_O, NULL},
{"type_freeze", type_freeze, METH_VARARGS},
{"test_atexit", test_atexit, METH_NOARGS},
{NULL, NULL} /* sentinel */
};

View File

@ -1236,39 +1236,6 @@ unicode_transformdecimalandspacetoascii(PyObject *self, PyObject *arg)
return _PyUnicode_TransformDecimalAndSpaceToASCII(arg);
}
struct atexit_data {
int called;
};
static void
callback(void *data)
{
((struct atexit_data *)data)->called += 1;
}
static PyObject *
test_atexit(PyObject *self, PyObject *Py_UNUSED(args))
{
PyThreadState *oldts = PyThreadState_Swap(NULL);
PyThreadState *tstate = Py_NewInterpreter();
struct atexit_data data = {0};
int res = PyUnstable_AtExit(tstate->interp, callback, (void *)&data);
Py_EndInterpreter(tstate);
PyThreadState_Swap(oldts);
if (res < 0) {
return NULL;
}
if (data.called == 0) {
PyErr_SetString(PyExc_RuntimeError, "atexit callback not called");
return NULL;
}
Py_RETURN_NONE;
}
static PyObject *
test_pyobject_is_freed(const char *test_name, PyObject *op)
{
@ -2128,7 +2095,6 @@ static PyMethodDef module_functions[] = {
{"_PyTraceMalloc_GetTraceback", tracemalloc_get_traceback, METH_VARARGS},
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
{"_PyUnicode_TransformDecimalAndSpaceToASCII", unicode_transformdecimalandspacetoascii, METH_O},
{"test_atexit", test_atexit, METH_NOARGS},
{"check_pyobject_forbidden_bytes_is_freed",
check_pyobject_forbidden_bytes_is_freed, METH_NOARGS},
{"check_pyobject_freed_is_freed", check_pyobject_freed_is_freed, METH_NOARGS},

View File

@ -27,7 +27,10 @@ int
PyUnstable_AtExit(PyInterpreterState *interp,
atexit_datacallbackfunc func, void *data)
{
assert(interp == _PyInterpreterState_GET());
PyThreadState *tstate = _PyThreadState_GET();
_Py_EnsureTstateNotNULL(tstate);
assert(tstate->interp == interp);
atexit_callback *callback = PyMem_Malloc(sizeof(atexit_callback));
if (callback == NULL) {
PyErr_NoMemory();
@ -38,12 +41,13 @@ PyUnstable_AtExit(PyInterpreterState *interp,
callback->next = NULL;
struct atexit_state *state = &interp->atexit;
if (state->ll_callbacks == NULL) {
atexit_callback *top = state->ll_callbacks;
if (top == NULL) {
state->ll_callbacks = callback;
state->last_ll_callback = callback;
}
else {
state->last_ll_callback->next = callback;
callback->next = top;
state->ll_callbacks = callback;
}
return 0;
}