Skip to content

bpo-42639: Move atexit state to PyInterpreterState #23763

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,20 @@ struct _Py_exc_state {
};


// atexit state
typedef struct {
PyObject *func;
PyObject *args;
PyObject *kwargs;
} atexit_callback;

struct atexit_state {
atexit_callback **callbacks;
int ncallbacks;
int callback_len;
};


/* interpreter state */

#define _PY_NSMALLPOSINTS 257
Expand Down Expand Up @@ -234,12 +248,10 @@ struct _is {
PyObject *after_forkers_child;
#endif

/* AtExit module */
PyObject *atexit_module;

uint64_t tstate_next_unique_id;

struct _warnings_runtime_state warnings;
struct atexit_state atexit;

PyObject *audit_hooks;

Expand Down
4 changes: 3 additions & 1 deletion Include/internal/pycore_pylifecycle.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ extern PyStatus _PyTypes_Init(void);
extern PyStatus _PyTypes_InitSlotDefs(void);
extern PyStatus _PyImportZip_Init(PyThreadState *tstate);
extern PyStatus _PyGC_Init(PyThreadState *tstate);
extern PyStatus _PyAtExit_Init(PyThreadState *tstate);


/* Various internal finalizers */
Expand Down Expand Up @@ -85,6 +86,7 @@ extern void _PyHash_Fini(void);
extern void _PyTraceMalloc_Fini(void);
extern void _PyWarnings_Fini(PyInterpreterState *interp);
extern void _PyAST_Fini(PyInterpreterState *interp);
extern void _PyAtExit_Fini(PyInterpreterState *interp);

extern PyStatus _PyGILState_Init(PyThreadState *tstate);
extern void _PyGILState_Fini(PyThreadState *tstate);
Expand All @@ -109,7 +111,7 @@ PyAPI_FUNC(void) _PyErr_Display(PyObject *file, PyObject *exception,

PyAPI_FUNC(void) _PyThreadState_DeleteCurrent(PyThreadState *tstate);

extern void _PyAtExit_Call(PyObject *module);
extern void _PyAtExit_Call(PyThreadState *tstate);

#ifdef __cplusplus
}
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_atexit.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,24 @@ def f(msg):
self.assertEqual(res.out.decode().splitlines(), ["two", "one"])
self.assertFalse(res.err)

def test_atexit_instances(self):
# bpo-42639: It is safe to have more than one atexit instance.
code = textwrap.dedent("""
import sys
import atexit as atexit1
del sys.modules['atexit']
import atexit as atexit2
del sys.modules['atexit']

assert atexit2 is not atexit1

atexit1.register(print, "atexit1")
atexit2.register(print, "atexit2")
""")
res = script_helper.assert_python_ok("-c", code)
self.assertEqual(res.out.decode().splitlines(), ["atexit2", "atexit1"])
self.assertFalse(res.err)


@support.cpython_only
class SubinterpreterTest(unittest.TestCase):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Make the :mod:`atexit` module state per-interpreter. It is now safe have more
than one :mod:`atexit` module instance.
Patch by Dong-hee Na and Victor Stinner.
150 changes: 53 additions & 97 deletions Modules/atexitmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,26 @@
*/

#include "Python.h"
#include "pycore_interp.h" // PyInterpreterState.atexit_func
#include "pycore_initconfig.h" // _PyStatus_NO_MEMORY
#include "pycore_interp.h" // PyInterpreterState.atexit
#include "pycore_pystate.h" // _PyInterpreterState_GET

/* ===================================================================== */
/* Callback machinery. */

typedef struct {
PyObject *func;
PyObject *args;
PyObject *kwargs;
} atexit_callback;

struct atexit_state {
atexit_callback **atexit_callbacks;
int ncallbacks;
int callback_len;
};

static inline struct atexit_state*
get_atexit_state(PyObject *module)
get_atexit_state(void)
{
void *state = PyModule_GetState(module);
assert(state != NULL);
return (struct atexit_state *)state;
PyInterpreterState *interp = _PyInterpreterState_GET();
return &interp->atexit;
}


static void
atexit_delete_cb(struct atexit_state *state, int i)
{
atexit_callback *cb = state->atexit_callbacks[i];
state->atexit_callbacks[i] = NULL;
atexit_callback *cb = state->callbacks[i];
state->callbacks[i] = NULL;

Py_DECREF(cb->func);
Py_DECREF(cb->args);
Expand All @@ -53,7 +41,7 @@ atexit_cleanup(struct atexit_state *state)
{
atexit_callback *cb;
for (int i = 0; i < state->ncallbacks; i++) {
cb = state->atexit_callbacks[i];
cb = state->callbacks[i];
if (cb == NULL)
continue;

Expand All @@ -62,25 +50,45 @@ atexit_cleanup(struct atexit_state *state)
state->ncallbacks = 0;
}

/* Installed into pylifecycle.c's atexit mechanism */

static void
atexit_callfuncs(PyObject *module, int ignore_exc)
PyStatus
_PyAtExit_Init(PyThreadState *tstate)
{
assert(!PyErr_Occurred());
struct atexit_state *state = &tstate->interp->atexit;
// _PyAtExit_Init() must only be called once
assert(state->callbacks == NULL);

if (module == NULL) {
return;
state->callback_len = 32;
state->ncallbacks = 0;
state->callbacks = PyMem_New(atexit_callback*, state->callback_len);
if (state->callbacks == NULL) {
return _PyStatus_NO_MEMORY();
}
return _PyStatus_OK();
}


void
_PyAtExit_Fini(PyInterpreterState *interp)
{
struct atexit_state *state = &interp->atexit;
atexit_cleanup(state);
PyMem_Free(state->callbacks);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be safer to set it to NULL after freeing.

}


static void
atexit_callfuncs(struct atexit_state *state, int ignore_exc)
{
assert(!PyErr_Occurred());

struct atexit_state *state = get_atexit_state(module);
if (state->ncallbacks == 0) {
return;
}

PyObject *exc_type = NULL, *exc_value, *exc_tb;
for (int i = state->ncallbacks - 1; i >= 0; i--) {
atexit_callback *cb = state->atexit_callbacks[i];
atexit_callback *cb = state->callbacks[i];
if (cb == NULL) {
continue;
}
Expand Down Expand Up @@ -125,15 +133,17 @@ atexit_callfuncs(PyObject *module, int ignore_exc)


void
_PyAtExit_Call(PyObject *module)
_PyAtExit_Call(PyThreadState *tstate)
{
atexit_callfuncs(module, 1);
struct atexit_state *state = &tstate->interp->atexit;
atexit_callfuncs(state, 1);
}


/* ===================================================================== */
/* Module methods. */


PyDoc_STRVAR(atexit_register__doc__,
"register(func, *args, **kwargs) -> func\n\
\n\
Expand Down Expand Up @@ -161,15 +171,15 @@ atexit_register(PyObject *module, PyObject *args, PyObject *kwargs)
return NULL;
}

struct atexit_state *state = get_atexit_state(module);
struct atexit_state *state = get_atexit_state();
if (state->ncallbacks >= state->callback_len) {
atexit_callback **r;
state->callback_len += 16;
r = (atexit_callback**)PyMem_Realloc(state->atexit_callbacks,
sizeof(atexit_callback*) * state->callback_len);
size_t size = sizeof(atexit_callback*) * (size_t)state->callback_len;
r = (atexit_callback**)PyMem_Realloc(state->callbacks, size);
if (r == NULL)
return PyErr_NoMemory();
state->atexit_callbacks = r;
state->callbacks = r;
}

atexit_callback *callback = PyMem_Malloc(sizeof(atexit_callback));
Expand All @@ -185,7 +195,7 @@ atexit_register(PyObject *module, PyObject *args, PyObject *kwargs)
callback->func = Py_NewRef(func);
callback->kwargs = Py_XNewRef(kwargs);

state->atexit_callbacks[state->ncallbacks++] = callback;
state->callbacks[state->ncallbacks++] = callback;

return Py_NewRef(func);
}
Expand All @@ -198,7 +208,8 @@ Run all registered exit functions.");
static PyObject *
atexit_run_exitfuncs(PyObject *module, PyObject *unused)
{
atexit_callfuncs(module, 0);
struct atexit_state *state = get_atexit_state();
atexit_callfuncs(state, 0);
if (PyErr_Occurred()) {
return NULL;
}
Expand All @@ -213,7 +224,7 @@ Clear the list of previously registered exit functions.");
static PyObject *
atexit_clear(PyObject *module, PyObject *unused)
{
atexit_cleanup(get_atexit_state(module));
atexit_cleanup(get_atexit_state());
Py_RETURN_NONE;
}

Expand All @@ -225,41 +236,10 @@ Return the number of registered exit functions.");
static PyObject *
atexit_ncallbacks(PyObject *module, PyObject *unused)
{
struct atexit_state *state = get_atexit_state(module);
struct atexit_state *state = get_atexit_state();
return PyLong_FromSsize_t(state->ncallbacks);
}

static int
atexit_m_traverse(PyObject *module, visitproc visit, void *arg)
{
struct atexit_state *state = (struct atexit_state *)PyModule_GetState(module);
for (int i = 0; i < state->ncallbacks; i++) {
atexit_callback *cb = state->atexit_callbacks[i];
if (cb == NULL)
continue;
Py_VISIT(cb->func);
Py_VISIT(cb->args);
Py_VISIT(cb->kwargs);
}
return 0;
}

static int
atexit_m_clear(PyObject *module)
{
struct atexit_state *state = (struct atexit_state *)PyModule_GetState(module);
atexit_cleanup(state);
return 0;
}

static void
atexit_free(PyObject *module)
{
struct atexit_state *state = (struct atexit_state *)PyModule_GetState(module);
atexit_cleanup(state);
PyMem_Free(state->atexit_callbacks);
}

PyDoc_STRVAR(atexit_unregister__doc__,
"unregister(func) -> None\n\
\n\
Expand All @@ -271,10 +251,10 @@ atexit.register\n\
static PyObject *
atexit_unregister(PyObject *module, PyObject *func)
{
struct atexit_state *state = get_atexit_state(module);
struct atexit_state *state = get_atexit_state();
for (int i = 0; i < state->ncallbacks; i++)
{
atexit_callback *cb = state->atexit_callbacks[i];
atexit_callback *cb = state->callbacks[i];
if (cb == NULL) {
continue;
}
Expand Down Expand Up @@ -305,6 +285,7 @@ static PyMethodDef atexit_methods[] = {
{NULL, NULL} /* sentinel */
};


/* ===================================================================== */
/* Initialization function. */

Expand All @@ -315,37 +296,12 @@ upon normal program termination.\n\
Two public functions, register and unregister, are defined.\n\
");

static int
atexit_exec(PyObject *module)
{
struct atexit_state *state = get_atexit_state(module);
state->callback_len = 32;
state->ncallbacks = 0;
state->atexit_callbacks = PyMem_New(atexit_callback*, state->callback_len);
if (state->atexit_callbacks == NULL) {
return -1;
}

PyInterpreterState *interp = _PyInterpreterState_GET();
interp->atexit_module = module;
return 0;
}

static PyModuleDef_Slot atexit_slots[] = {
{Py_mod_exec, atexit_exec},
{0, NULL}
};

static struct PyModuleDef atexitmodule = {
PyModuleDef_HEAD_INIT,
.m_name = "atexit",
.m_doc = atexit__doc__,
.m_size = sizeof(struct atexit_state),
.m_size = 0,
.m_methods = atexit_methods,
.m_slots = atexit_slots,
.m_traverse = atexit_m_traverse,
.m_clear = atexit_m_clear,
.m_free = (freefunc)atexit_free
};

PyMODINIT_FUNC
Expand Down
Loading