Kaydet (Commit) 2d350fd8 authored tarafından Antoine Pitrou's avatar Antoine Pitrou

Issue #18619: Fix atexit leaking callbacks registered from sub-interpreters, and make it GC-aware.

üst 7a2572cb
...@@ -2,6 +2,7 @@ import sys ...@@ -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(): ...@@ -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): ...@@ -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 ...@@ -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 @@ ...@@ -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 { ...@@ -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) ...@@ -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."); ...@@ -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) ...@@ -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) ...@@ -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[] = { ...@@ -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\ ...@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment