mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Issue #18619: Fix atexit leaking callbacks registered from sub-interpreters, and make it GC-aware.
This commit is contained in:
parent
7a2572cb49
commit
2d350fd8af
3 changed files with 122 additions and 44 deletions
|
@ -2,6 +2,7 @@ import sys
|
||||||
import unittest
|
import unittest
|
||||||
import io
|
import io
|
||||||
import atexit
|
import atexit
|
||||||
|
import _testcapi
|
||||||
from test import support
|
from test import support
|
||||||
|
|
||||||
### helpers
|
### helpers
|
||||||
|
@ -23,7 +24,9 @@ def raise1():
|
||||||
def raise2():
|
def raise2():
|
||||||
raise SystemError
|
raise SystemError
|
||||||
|
|
||||||
class TestCase(unittest.TestCase):
|
|
||||||
|
class GeneralTest(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.save_stdout = sys.stdout
|
self.save_stdout = sys.stdout
|
||||||
self.save_stderr = sys.stderr
|
self.save_stderr = sys.stderr
|
||||||
|
@ -122,8 +125,43 @@ class TestCase(unittest.TestCase):
|
||||||
self.assertEqual(l, [5])
|
self.assertEqual(l, [5])
|
||||||
|
|
||||||
|
|
||||||
|
class SubinterpreterTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_callbacks_leak(self):
|
||||||
|
# This test shows a leak in refleak mode if atexit doesn't
|
||||||
|
# take care to free callbacks in its per-subinterpreter module
|
||||||
|
# state.
|
||||||
|
n = atexit._ncallbacks()
|
||||||
|
code = r"""if 1:
|
||||||
|
import atexit
|
||||||
|
def f():
|
||||||
|
pass
|
||||||
|
atexit.register(f)
|
||||||
|
del atexit
|
||||||
|
"""
|
||||||
|
ret = _testcapi.run_in_subinterp(code)
|
||||||
|
self.assertEqual(ret, 0)
|
||||||
|
self.assertEqual(atexit._ncallbacks(), n)
|
||||||
|
|
||||||
|
def test_callbacks_leak_refcycle(self):
|
||||||
|
# Similar to the above, but with a refcycle through the atexit
|
||||||
|
# module.
|
||||||
|
n = atexit._ncallbacks()
|
||||||
|
code = r"""if 1:
|
||||||
|
import atexit
|
||||||
|
def f():
|
||||||
|
pass
|
||||||
|
atexit.register(f)
|
||||||
|
atexit.__atexit = atexit
|
||||||
|
"""
|
||||||
|
ret = _testcapi.run_in_subinterp(code)
|
||||||
|
self.assertEqual(ret, 0)
|
||||||
|
self.assertEqual(atexit._ncallbacks(), n)
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
support.run_unittest(TestCase)
|
support.run_unittest(__name__)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_main()
|
test_main()
|
||||||
|
|
|
@ -179,6 +179,9 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #18619: Fix atexit leaking callbacks registered from sub-interpreters,
|
||||||
|
and make it GC-aware.
|
||||||
|
|
||||||
- Issue #15699: The readline module now uses PEP 3121-style module
|
- Issue #15699: The readline module now uses PEP 3121-style module
|
||||||
initialization, so as to reclaim allocated resources (Python callbacks)
|
initialization, so as to reclaim allocated resources (Python callbacks)
|
||||||
at shutdown. Original patch by Robin Schreiber.
|
at shutdown. Original patch by Robin Schreiber.
|
||||||
|
|
|
@ -10,8 +10,6 @@
|
||||||
|
|
||||||
/* Forward declaration (for atexit_cleanup) */
|
/* Forward declaration (for atexit_cleanup) */
|
||||||
static PyObject *atexit_clear(PyObject*, PyObject*);
|
static PyObject *atexit_clear(PyObject*, PyObject*);
|
||||||
/* Forward declaration (for atexit_callfuncs) */
|
|
||||||
static void atexit_cleanup(PyObject*);
|
|
||||||
/* Forward declaration of module object */
|
/* Forward declaration of module object */
|
||||||
static struct PyModuleDef atexitmodule;
|
static struct PyModuleDef atexitmodule;
|
||||||
|
|
||||||
|
@ -33,6 +31,35 @@ typedef struct {
|
||||||
#define GET_ATEXIT_STATE(mod) ((atexitmodule_state*)PyModule_GetState(mod))
|
#define GET_ATEXIT_STATE(mod) ((atexitmodule_state*)PyModule_GetState(mod))
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
atexit_delete_cb(atexitmodule_state *modstate, int i)
|
||||||
|
{
|
||||||
|
atexit_callback *cb;
|
||||||
|
|
||||||
|
cb = modstate->atexit_callbacks[i];
|
||||||
|
modstate->atexit_callbacks[i] = NULL;
|
||||||
|
Py_DECREF(cb->func);
|
||||||
|
Py_DECREF(cb->args);
|
||||||
|
Py_XDECREF(cb->kwargs);
|
||||||
|
PyMem_Free(cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear all callbacks without calling them */
|
||||||
|
static void
|
||||||
|
atexit_cleanup(atexitmodule_state *modstate)
|
||||||
|
{
|
||||||
|
atexit_callback *cb;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < modstate->ncallbacks; i++) {
|
||||||
|
cb = modstate->atexit_callbacks[i];
|
||||||
|
if (cb == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
atexit_delete_cb(modstate, i);
|
||||||
|
}
|
||||||
|
modstate->ncallbacks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Installed into pythonrun.c's atexit mechanism */
|
/* Installed into pythonrun.c's atexit mechanism */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -78,34 +105,12 @@ atexit_callfuncs(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
atexit_cleanup(module);
|
atexit_cleanup(modstate);
|
||||||
|
|
||||||
if (exc_type)
|
if (exc_type)
|
||||||
PyErr_Restore(exc_type, exc_value, exc_tb);
|
PyErr_Restore(exc_type, exc_value, exc_tb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
atexit_delete_cb(PyObject *self, int i)
|
|
||||||
{
|
|
||||||
atexitmodule_state *modstate;
|
|
||||||
atexit_callback *cb;
|
|
||||||
|
|
||||||
modstate = GET_ATEXIT_STATE(self);
|
|
||||||
cb = modstate->atexit_callbacks[i];
|
|
||||||
modstate->atexit_callbacks[i] = NULL;
|
|
||||||
Py_DECREF(cb->func);
|
|
||||||
Py_DECREF(cb->args);
|
|
||||||
Py_XDECREF(cb->kwargs);
|
|
||||||
PyMem_Free(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
atexit_cleanup(PyObject *self)
|
|
||||||
{
|
|
||||||
PyObject *r = atexit_clear(self, NULL);
|
|
||||||
Py_DECREF(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===================================================================== */
|
/* ===================================================================== */
|
||||||
/* Module methods. */
|
/* Module methods. */
|
||||||
|
|
||||||
|
@ -193,22 +198,51 @@ Clear the list of previously registered exit functions.");
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
atexit_clear(PyObject *self, PyObject *unused)
|
atexit_clear(PyObject *self, PyObject *unused)
|
||||||
|
{
|
||||||
|
atexit_cleanup(GET_ATEXIT_STATE(self));
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(atexit_ncallbacks__doc__,
|
||||||
|
"_ncallbacks() -> int\n\
|
||||||
|
\n\
|
||||||
|
Return the number of registered exit functions.");
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
atexit_ncallbacks(PyObject *self, PyObject *unused)
|
||||||
{
|
{
|
||||||
atexitmodule_state *modstate;
|
atexitmodule_state *modstate;
|
||||||
atexit_callback *cb;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
modstate = GET_ATEXIT_STATE(self);
|
modstate = GET_ATEXIT_STATE(self);
|
||||||
|
|
||||||
|
return PyLong_FromSsize_t(modstate->ncallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
atexit_m_traverse(PyObject *self, visitproc visit, void *arg)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
atexitmodule_state *modstate;
|
||||||
|
|
||||||
|
modstate = GET_ATEXIT_STATE(self);
|
||||||
for (i = 0; i < modstate->ncallbacks; i++) {
|
for (i = 0; i < modstate->ncallbacks; i++) {
|
||||||
cb = modstate->atexit_callbacks[i];
|
atexit_callback *cb = modstate->atexit_callbacks[i];
|
||||||
if (cb == NULL)
|
if (cb == NULL)
|
||||||
continue;
|
continue;
|
||||||
|
Py_VISIT(cb->func);
|
||||||
atexit_delete_cb(self, i);
|
Py_VISIT(cb->args);
|
||||||
|
Py_VISIT(cb->kwargs);
|
||||||
}
|
}
|
||||||
modstate->ncallbacks = 0;
|
return 0;
|
||||||
Py_RETURN_NONE;
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
atexit_m_clear(PyObject *self)
|
||||||
|
{
|
||||||
|
atexitmodule_state *modstate;
|
||||||
|
modstate = GET_ATEXIT_STATE(self);
|
||||||
|
atexit_cleanup(modstate);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -216,6 +250,7 @@ atexit_free(PyObject *m)
|
||||||
{
|
{
|
||||||
atexitmodule_state *modstate;
|
atexitmodule_state *modstate;
|
||||||
modstate = GET_ATEXIT_STATE(m);
|
modstate = GET_ATEXIT_STATE(m);
|
||||||
|
atexit_cleanup(modstate);
|
||||||
PyMem_Free(modstate->atexit_callbacks);
|
PyMem_Free(modstate->atexit_callbacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +281,7 @@ atexit_unregister(PyObject *self, PyObject *func)
|
||||||
if (eq < 0)
|
if (eq < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
if (eq)
|
if (eq)
|
||||||
atexit_delete_cb(self, i);
|
atexit_delete_cb(modstate, i);
|
||||||
}
|
}
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
@ -260,6 +295,8 @@ static PyMethodDef atexit_methods[] = {
|
||||||
atexit_unregister__doc__},
|
atexit_unregister__doc__},
|
||||||
{"_run_exitfuncs", (PyCFunction) atexit_run_exitfuncs, METH_NOARGS,
|
{"_run_exitfuncs", (PyCFunction) atexit_run_exitfuncs, METH_NOARGS,
|
||||||
atexit_run_exitfuncs__doc__},
|
atexit_run_exitfuncs__doc__},
|
||||||
|
{"_ncallbacks", (PyCFunction) atexit_ncallbacks, METH_NOARGS,
|
||||||
|
atexit_ncallbacks__doc__},
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -275,15 +312,15 @@ Two public functions, register and unregister, are defined.\n\
|
||||||
|
|
||||||
|
|
||||||
static struct PyModuleDef atexitmodule = {
|
static struct PyModuleDef atexitmodule = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
"atexit",
|
"atexit",
|
||||||
atexit__doc__,
|
atexit__doc__,
|
||||||
sizeof(atexitmodule_state),
|
sizeof(atexitmodule_state),
|
||||||
atexit_methods,
|
atexit_methods,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
atexit_m_traverse,
|
||||||
NULL,
|
atexit_m_clear,
|
||||||
(freefunc)atexit_free
|
(freefunc)atexit_free
|
||||||
};
|
};
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
PyMODINIT_FUNC
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue