gh-127791: Fix, document, and test PyUnstable_AtExit
(#127793)
This commit is contained in:
parent
2cdeb61b57
commit
d5d84c3f13
@ -567,6 +567,15 @@ Initializing and finalizing the interpreter
|
|||||||
customized Python that always runs in isolated mode using
|
customized Python that always runs in isolated mode using
|
||||||
:c:func:`Py_RunMain`.
|
: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
|
Process-wide parameters
|
||||||
=======================
|
=======================
|
||||||
|
@ -426,3 +426,7 @@ Process Control
|
|||||||
function registered last is called first. Each cleanup function will be called
|
function registered last is called first. Each cleanup function will be called
|
||||||
at most once. Since Python's internal finalization will have completed before
|
at most once. Since Python's internal finalization will have completed before
|
||||||
the cleanup function, no Python APIs should be called by *func*.
|
the cleanup function, no Python APIs should be called by *func*.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
:c:func:`PyUnstable_AtExit` for passing a ``void *data`` argument.
|
||||||
|
@ -44,7 +44,6 @@ typedef struct {
|
|||||||
|
|
||||||
struct atexit_state {
|
struct atexit_state {
|
||||||
atexit_callback *ll_callbacks;
|
atexit_callback *ll_callbacks;
|
||||||
atexit_callback *last_ll_callback;
|
|
||||||
|
|
||||||
// XXX The rest of the state could be moved to the atexit module state
|
// 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.
|
// and a low-level callback added for it during module exec.
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
Fix loss of callbacks after more than one call to
|
||||||
|
:c:func:`PyUnstable_AtExit`.
|
@ -3353,6 +3353,53 @@ type_freeze(PyObject *module, PyObject *args)
|
|||||||
Py_RETURN_NONE;
|
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[] = {
|
static PyMethodDef TestMethods[] = {
|
||||||
{"set_errno", set_errno, METH_VARARGS},
|
{"set_errno", set_errno, METH_VARARGS},
|
||||||
@ -3495,6 +3542,7 @@ static PyMethodDef TestMethods[] = {
|
|||||||
{"test_critical_sections", test_critical_sections, METH_NOARGS},
|
{"test_critical_sections", test_critical_sections, METH_NOARGS},
|
||||||
{"finalize_thread_hang", finalize_thread_hang, METH_O, NULL},
|
{"finalize_thread_hang", finalize_thread_hang, METH_O, NULL},
|
||||||
{"type_freeze", type_freeze, METH_VARARGS},
|
{"type_freeze", type_freeze, METH_VARARGS},
|
||||||
|
{"test_atexit", test_atexit, METH_NOARGS},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1236,39 +1236,6 @@ unicode_transformdecimalandspacetoascii(PyObject *self, PyObject *arg)
|
|||||||
return _PyUnicode_TransformDecimalAndSpaceToASCII(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 *
|
static PyObject *
|
||||||
test_pyobject_is_freed(const char *test_name, PyObject *op)
|
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},
|
{"_PyTraceMalloc_GetTraceback", tracemalloc_get_traceback, METH_VARARGS},
|
||||||
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
|
{"test_tstate_capi", test_tstate_capi, METH_NOARGS, NULL},
|
||||||
{"_PyUnicode_TransformDecimalAndSpaceToASCII", unicode_transformdecimalandspacetoascii, METH_O},
|
{"_PyUnicode_TransformDecimalAndSpaceToASCII", unicode_transformdecimalandspacetoascii, METH_O},
|
||||||
{"test_atexit", test_atexit, METH_NOARGS},
|
|
||||||
{"check_pyobject_forbidden_bytes_is_freed",
|
{"check_pyobject_forbidden_bytes_is_freed",
|
||||||
check_pyobject_forbidden_bytes_is_freed, METH_NOARGS},
|
check_pyobject_forbidden_bytes_is_freed, METH_NOARGS},
|
||||||
{"check_pyobject_freed_is_freed", check_pyobject_freed_is_freed, METH_NOARGS},
|
{"check_pyobject_freed_is_freed", check_pyobject_freed_is_freed, METH_NOARGS},
|
||||||
|
@ -27,7 +27,10 @@ int
|
|||||||
PyUnstable_AtExit(PyInterpreterState *interp,
|
PyUnstable_AtExit(PyInterpreterState *interp,
|
||||||
atexit_datacallbackfunc func, void *data)
|
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));
|
atexit_callback *callback = PyMem_Malloc(sizeof(atexit_callback));
|
||||||
if (callback == NULL) {
|
if (callback == NULL) {
|
||||||
PyErr_NoMemory();
|
PyErr_NoMemory();
|
||||||
@ -38,12 +41,13 @@ PyUnstable_AtExit(PyInterpreterState *interp,
|
|||||||
callback->next = NULL;
|
callback->next = NULL;
|
||||||
|
|
||||||
struct atexit_state *state = &interp->atexit;
|
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->ll_callbacks = callback;
|
||||||
state->last_ll_callback = callback;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
state->last_ll_callback->next = callback;
|
callback->next = top;
|
||||||
|
state->ll_callbacks = callback;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user