Skip to content

Commit b8fa135

Browse files
vstinnercorona10
andauthored
bpo-42639: Move atexit state to PyInterpreterState (GH-23763)
* Add _PyAtExit_Call() function and remove pyexitfunc and pyexitmodule members of PyInterpreterState. The function logs atexit callback errors using _PyErr_WriteUnraisableMsg(). * Add _PyAtExit_Init() and _PyAtExit_Fini() functions. * Remove traverse, clear and free functions of the atexit module. Co-authored-by: Dong-hee Na <[email protected]>
1 parent 8473cf8 commit b8fa135

File tree

7 files changed

+101
-113
lines changed

7 files changed

+101
-113
lines changed

Include/internal/pycore_interp.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,20 @@ struct _Py_exc_state {
159159
};
160160

161161

162+
// atexit state
163+
typedef struct {
164+
PyObject *func;
165+
PyObject *args;
166+
PyObject *kwargs;
167+
} atexit_callback;
168+
169+
struct atexit_state {
170+
atexit_callback **callbacks;
171+
int ncallbacks;
172+
int callback_len;
173+
};
174+
175+
162176
/* interpreter state */
163177

164178
#define _PY_NSMALLPOSINTS 257
@@ -234,12 +248,10 @@ struct _is {
234248
PyObject *after_forkers_child;
235249
#endif
236250

237-
/* AtExit module */
238-
PyObject *atexit_module;
239-
240251
uint64_t tstate_next_unique_id;
241252

242253
struct _warnings_runtime_state warnings;
254+
struct atexit_state atexit;
243255

244256
PyObject *audit_hooks;
245257

Include/internal/pycore_pylifecycle.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ extern PyStatus _PyTypes_Init(void);
5555
extern PyStatus _PyTypes_InitSlotDefs(void);
5656
extern PyStatus _PyImportZip_Init(PyThreadState *tstate);
5757
extern PyStatus _PyGC_Init(PyThreadState *tstate);
58+
extern PyStatus _PyAtExit_Init(PyThreadState *tstate);
5859

5960

6061
/* Various internal finalizers */
@@ -85,6 +86,7 @@ extern void _PyHash_Fini(void);
8586
extern void _PyTraceMalloc_Fini(void);
8687
extern void _PyWarnings_Fini(PyInterpreterState *interp);
8788
extern void _PyAST_Fini(PyInterpreterState *interp);
89+
extern void _PyAtExit_Fini(PyInterpreterState *interp);
8890

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

110112
PyAPI_FUNC(void) _PyThreadState_DeleteCurrent(PyThreadState *tstate);
111113

112-
extern void _PyAtExit_Call(PyObject *module);
114+
extern void _PyAtExit_Call(PyThreadState *tstate);
113115

114116
#ifdef __cplusplus
115117
}

Lib/test/test_atexit.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,24 @@ def f(msg):
170170
self.assertEqual(res.out.decode().splitlines(), ["two", "one"])
171171
self.assertFalse(res.err)
172172

173+
def test_atexit_instances(self):
174+
# bpo-42639: It is safe to have more than one atexit instance.
175+
code = textwrap.dedent("""
176+
import sys
177+
import atexit as atexit1
178+
del sys.modules['atexit']
179+
import atexit as atexit2
180+
del sys.modules['atexit']
181+
182+
assert atexit2 is not atexit1
183+
184+
atexit1.register(print, "atexit1")
185+
atexit2.register(print, "atexit2")
186+
""")
187+
res = script_helper.assert_python_ok("-c", code)
188+
self.assertEqual(res.out.decode().splitlines(), ["atexit2", "atexit1"])
189+
self.assertFalse(res.err)
190+
173191

174192
@support.cpython_only
175193
class SubinterpreterTest(unittest.TestCase):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Make the :mod:`atexit` module state per-interpreter. It is now safe have more
2+
than one :mod:`atexit` module instance.
3+
Patch by Dong-hee Na and Victor Stinner.

Modules/atexitmodule.c

Lines changed: 53 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,26 @@
77
*/
88

99
#include "Python.h"
10-
#include "pycore_interp.h" // PyInterpreterState.atexit_func
10+
#include "pycore_initconfig.h" // _PyStatus_NO_MEMORY
11+
#include "pycore_interp.h" // PyInterpreterState.atexit
1112
#include "pycore_pystate.h" // _PyInterpreterState_GET
1213

1314
/* ===================================================================== */
1415
/* Callback machinery. */
1516

16-
typedef struct {
17-
PyObject *func;
18-
PyObject *args;
19-
PyObject *kwargs;
20-
} atexit_callback;
21-
22-
struct atexit_state {
23-
atexit_callback **atexit_callbacks;
24-
int ncallbacks;
25-
int callback_len;
26-
};
27-
2817
static inline struct atexit_state*
29-
get_atexit_state(PyObject *module)
18+
get_atexit_state(void)
3019
{
31-
void *state = PyModule_GetState(module);
32-
assert(state != NULL);
33-
return (struct atexit_state *)state;
20+
PyInterpreterState *interp = _PyInterpreterState_GET();
21+
return &interp->atexit;
3422
}
3523

3624

3725
static void
3826
atexit_delete_cb(struct atexit_state *state, int i)
3927
{
40-
atexit_callback *cb = state->atexit_callbacks[i];
41-
state->atexit_callbacks[i] = NULL;
28+
atexit_callback *cb = state->callbacks[i];
29+
state->callbacks[i] = NULL;
4230

4331
Py_DECREF(cb->func);
4432
Py_DECREF(cb->args);
@@ -53,7 +41,7 @@ atexit_cleanup(struct atexit_state *state)
5341
{
5442
atexit_callback *cb;
5543
for (int i = 0; i < state->ncallbacks; i++) {
56-
cb = state->atexit_callbacks[i];
44+
cb = state->callbacks[i];
5745
if (cb == NULL)
5846
continue;
5947

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

65-
/* Installed into pylifecycle.c's atexit mechanism */
6653

67-
static void
68-
atexit_callfuncs(PyObject *module, int ignore_exc)
54+
PyStatus
55+
_PyAtExit_Init(PyThreadState *tstate)
6956
{
70-
assert(!PyErr_Occurred());
57+
struct atexit_state *state = &tstate->interp->atexit;
58+
// _PyAtExit_Init() must only be called once
59+
assert(state->callbacks == NULL);
7160

72-
if (module == NULL) {
73-
return;
61+
state->callback_len = 32;
62+
state->ncallbacks = 0;
63+
state->callbacks = PyMem_New(atexit_callback*, state->callback_len);
64+
if (state->callbacks == NULL) {
65+
return _PyStatus_NO_MEMORY();
7466
}
67+
return _PyStatus_OK();
68+
}
69+
70+
71+
void
72+
_PyAtExit_Fini(PyInterpreterState *interp)
73+
{
74+
struct atexit_state *state = &interp->atexit;
75+
atexit_cleanup(state);
76+
PyMem_Free(state->callbacks);
77+
}
78+
79+
80+
static void
81+
atexit_callfuncs(struct atexit_state *state, int ignore_exc)
82+
{
83+
assert(!PyErr_Occurred());
7584

76-
struct atexit_state *state = get_atexit_state(module);
7785
if (state->ncallbacks == 0) {
7886
return;
7987
}
8088

8189
PyObject *exc_type = NULL, *exc_value, *exc_tb;
8290
for (int i = state->ncallbacks - 1; i >= 0; i--) {
83-
atexit_callback *cb = state->atexit_callbacks[i];
91+
atexit_callback *cb = state->callbacks[i];
8492
if (cb == NULL) {
8593
continue;
8694
}
@@ -125,15 +133,17 @@ atexit_callfuncs(PyObject *module, int ignore_exc)
125133

126134

127135
void
128-
_PyAtExit_Call(PyObject *module)
136+
_PyAtExit_Call(PyThreadState *tstate)
129137
{
130-
atexit_callfuncs(module, 1);
138+
struct atexit_state *state = &tstate->interp->atexit;
139+
atexit_callfuncs(state, 1);
131140
}
132141

133142

134143
/* ===================================================================== */
135144
/* Module methods. */
136145

146+
137147
PyDoc_STRVAR(atexit_register__doc__,
138148
"register(func, *args, **kwargs) -> func\n\
139149
\n\
@@ -161,15 +171,15 @@ atexit_register(PyObject *module, PyObject *args, PyObject *kwargs)
161171
return NULL;
162172
}
163173

164-
struct atexit_state *state = get_atexit_state(module);
174+
struct atexit_state *state = get_atexit_state();
165175
if (state->ncallbacks >= state->callback_len) {
166176
atexit_callback **r;
167177
state->callback_len += 16;
168-
r = (atexit_callback**)PyMem_Realloc(state->atexit_callbacks,
169-
sizeof(atexit_callback*) * state->callback_len);
178+
size_t size = sizeof(atexit_callback*) * (size_t)state->callback_len;
179+
r = (atexit_callback**)PyMem_Realloc(state->callbacks, size);
170180
if (r == NULL)
171181
return PyErr_NoMemory();
172-
state->atexit_callbacks = r;
182+
state->callbacks = r;
173183
}
174184

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

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

190200
return Py_NewRef(func);
191201
}
@@ -198,7 +208,8 @@ Run all registered exit functions.");
198208
static PyObject *
199209
atexit_run_exitfuncs(PyObject *module, PyObject *unused)
200210
{
201-
atexit_callfuncs(module, 0);
211+
struct atexit_state *state = get_atexit_state();
212+
atexit_callfuncs(state, 0);
202213
if (PyErr_Occurred()) {
203214
return NULL;
204215
}
@@ -213,7 +224,7 @@ Clear the list of previously registered exit functions.");
213224
static PyObject *
214225
atexit_clear(PyObject *module, PyObject *unused)
215226
{
216-
atexit_cleanup(get_atexit_state(module));
227+
atexit_cleanup(get_atexit_state());
217228
Py_RETURN_NONE;
218229
}
219230

@@ -225,41 +236,10 @@ Return the number of registered exit functions.");
225236
static PyObject *
226237
atexit_ncallbacks(PyObject *module, PyObject *unused)
227238
{
228-
struct atexit_state *state = get_atexit_state(module);
239+
struct atexit_state *state = get_atexit_state();
229240
return PyLong_FromSsize_t(state->ncallbacks);
230241
}
231242

232-
static int
233-
atexit_m_traverse(PyObject *module, visitproc visit, void *arg)
234-
{
235-
struct atexit_state *state = (struct atexit_state *)PyModule_GetState(module);
236-
for (int i = 0; i < state->ncallbacks; i++) {
237-
atexit_callback *cb = state->atexit_callbacks[i];
238-
if (cb == NULL)
239-
continue;
240-
Py_VISIT(cb->func);
241-
Py_VISIT(cb->args);
242-
Py_VISIT(cb->kwargs);
243-
}
244-
return 0;
245-
}
246-
247-
static int
248-
atexit_m_clear(PyObject *module)
249-
{
250-
struct atexit_state *state = (struct atexit_state *)PyModule_GetState(module);
251-
atexit_cleanup(state);
252-
return 0;
253-
}
254-
255-
static void
256-
atexit_free(PyObject *module)
257-
{
258-
struct atexit_state *state = (struct atexit_state *)PyModule_GetState(module);
259-
atexit_cleanup(state);
260-
PyMem_Free(state->atexit_callbacks);
261-
}
262-
263243
PyDoc_STRVAR(atexit_unregister__doc__,
264244
"unregister(func) -> None\n\
265245
\n\
@@ -271,10 +251,10 @@ atexit.register\n\
271251
static PyObject *
272252
atexit_unregister(PyObject *module, PyObject *func)
273253
{
274-
struct atexit_state *state = get_atexit_state(module);
254+
struct atexit_state *state = get_atexit_state();
275255
for (int i = 0; i < state->ncallbacks; i++)
276256
{
277-
atexit_callback *cb = state->atexit_callbacks[i];
257+
atexit_callback *cb = state->callbacks[i];
278258
if (cb == NULL) {
279259
continue;
280260
}
@@ -305,6 +285,7 @@ static PyMethodDef atexit_methods[] = {
305285
{NULL, NULL} /* sentinel */
306286
};
307287

288+
308289
/* ===================================================================== */
309290
/* Initialization function. */
310291

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

318-
static int
319-
atexit_exec(PyObject *module)
320-
{
321-
struct atexit_state *state = get_atexit_state(module);
322-
state->callback_len = 32;
323-
state->ncallbacks = 0;
324-
state->atexit_callbacks = PyMem_New(atexit_callback*, state->callback_len);
325-
if (state->atexit_callbacks == NULL) {
326-
return -1;
327-
}
328-
329-
PyInterpreterState *interp = _PyInterpreterState_GET();
330-
interp->atexit_module = module;
331-
return 0;
332-
}
333-
334-
static PyModuleDef_Slot atexit_slots[] = {
335-
{Py_mod_exec, atexit_exec},
336-
{0, NULL}
337-
};
338-
339299
static struct PyModuleDef atexitmodule = {
340300
PyModuleDef_HEAD_INIT,
341301
.m_name = "atexit",
342302
.m_doc = atexit__doc__,
343-
.m_size = sizeof(struct atexit_state),
303+
.m_size = 0,
344304
.m_methods = atexit_methods,
345-
.m_slots = atexit_slots,
346-
.m_traverse = atexit_m_traverse,
347-
.m_clear = atexit_m_clear,
348-
.m_free = (freefunc)atexit_free
349305
};
350306

351307
PyMODINIT_FUNC

0 commit comments

Comments
 (0)