diff options
author | Collin Winter <collinw@gmail.com> | 2007-03-21 02:57:17 (GMT) |
---|---|---|
committer | Collin Winter <collinw@gmail.com> | 2007-03-21 02:57:17 (GMT) |
commit | 670e6921349dd408b6958a0c5d3b1486725f9beb (patch) | |
tree | 719d6b0e5348693a2d985dfabc5e9160f7f79108 | |
parent | 450ee81b227b093eeb0b7a933fe6ddc6dc768d4a (diff) | |
download | cpython-670e6921349dd408b6958a0c5d3b1486725f9beb.zip cpython-670e6921349dd408b6958a0c5d3b1486725f9beb.tar.gz cpython-670e6921349dd408b6958a0c5d3b1486725f9beb.tar.bz2 |
Patch #1680961: remove sys.exitfunc and replace it with a private C API. Also, reimplement atexit in C so it can take advantage of this private API.
-rw-r--r-- | Doc/lib/libatexit.tex | 36 | ||||
-rw-r--r-- | Doc/lib/libsys.tex | 13 | ||||
-rw-r--r-- | Include/pythonrun.h | 4 | ||||
-rw-r--r-- | Lib/atexit.py | 65 | ||||
-rw-r--r-- | Lib/test/test___all__.py | 1 | ||||
-rw-r--r-- | Lib/test/test_atexit.py | 173 | ||||
-rw-r--r-- | Misc/NEWS | 5 | ||||
-rw-r--r-- | Modules/Setup.dist | 1 | ||||
-rw-r--r-- | Modules/atexitmodule.c | 217 | ||||
-rw-r--r-- | Python/import.c | 2 | ||||
-rw-r--r-- | Python/pythonrun.c | 42 | ||||
-rw-r--r-- | Python/sysmodule.c | 3 | ||||
-rw-r--r-- | setup.py | 2 |
13 files changed, 360 insertions, 204 deletions
diff --git a/Doc/lib/libatexit.tex b/Doc/lib/libatexit.tex index 9798b57..04f1d49 100644 --- a/Doc/lib/libatexit.tex +++ b/Doc/lib/libatexit.tex @@ -1,32 +1,21 @@ \section{\module{atexit} --- Exit handlers} -\declaremodule{standard}{atexit} +\declaremodule{builtin}{atexit} \moduleauthor{Skip Montanaro}{skip@mojam.com} \sectionauthor{Skip Montanaro}{skip@mojam.com} \modulesynopsis{Register and execute cleanup functions.} \versionadded{2.0} -The \module{atexit} module defines a single function to register -cleanup functions. Functions thus registered are automatically -executed upon normal interpreter termination. -Note: the functions registered via this module are not called when the program is killed by a -signal, when a Python fatal internal error is detected, or when -\function{os._exit()} is called. +The \module{atexit} module defines functions to register and +unregister cleanup functions. Functions thus registered are +automatically executed upon normal interpreter termination. -This is an alternate interface to the functionality provided by the -\code{sys.exitfunc} variable. -\withsubitem{(in sys)}{\ttindex{exitfunc}} - -Note: This module is unlikely to work correctly when used with other code -that sets \code{sys.exitfunc}. In particular, other core Python modules are -free to use \module{atexit} without the programmer's knowledge. Authors who -use \code{sys.exitfunc} should convert their code to use -\module{atexit} instead. The simplest way to convert code that sets -\code{sys.exitfunc} is to import \module{atexit} and register the function -that had been bound to \code{sys.exitfunc}. +Note: the functions registered via this module are not called when +the program is killed by a signal, when a Python fatal internal +error is detected, or when \function{os._exit()} is called. \begin{funcdesc}{register}{func\optional{, *args\optional{, **kargs}}} Register \var{func} as a function to be executed at termination. Any @@ -47,7 +36,16 @@ chance to run the last exception to be raised is re-raised. \versionchanged[This function now returns \var{func} which makes it possible to use it as a decorator without binding the - original name to \code{None}]{2.6} + original name to \code{None}]{2.6} +\end{funcdesc} + +\begin{funcdesc}{unregister}{func} +Remove a function \var{func} from the list of functions to be run at +interpreter-shutdown. After calling \function{unregister()}, +\var{func} is guaranteed not to be called when the interpreter +shuts down. + +\versionadded{3.0} \end{funcdesc} diff --git a/Doc/lib/libsys.tex b/Doc/lib/libsys.tex index 7a8859b..ac161d9 100644 --- a/Doc/lib/libsys.tex +++ b/Doc/lib/libsys.tex @@ -218,19 +218,6 @@ It is always available. program when an error occurs. \end{funcdesc} -\begin{datadesc}{exitfunc} - This value is not actually defined by the module, but can be set by - the user (or by a program) to specify a clean-up action at program - exit. When set, it should be a parameterless function. This - function will be called when the interpreter exits. Only one - function may be installed in this way; to allow multiple functions - which will be called at termination, use the \refmodule{atexit} - module. \note{The exit function is not called when the program is - killed by a signal, when a Python fatal internal error is detected, - or when \code{os._exit()} is called.} - \deprecated{2.4}{Use \refmodule{atexit} instead.} -\end{datadesc} - \begin{funcdesc}{getcheckinterval}{} Return the interpreter's ``check interval''; see \function{setcheckinterval()}. diff --git a/Include/pythonrun.h b/Include/pythonrun.h index e0979b5..08278cf 100644 --- a/Include/pythonrun.h +++ b/Include/pythonrun.h @@ -69,6 +69,10 @@ PyAPI_FUNC(void) PyErr_Print(void); PyAPI_FUNC(void) PyErr_PrintEx(int); PyAPI_FUNC(void) PyErr_Display(PyObject *, PyObject *, PyObject *); +/* Py_PyAtExit is for the atexit module, Py_AtExit is for low-level + * exit functions. + */ +PyAPI_FUNC(void) _Py_PyAtExit(void (*func)(void)); PyAPI_FUNC(int) Py_AtExit(void (*func)(void)); PyAPI_FUNC(void) Py_Exit(int); diff --git a/Lib/atexit.py b/Lib/atexit.py deleted file mode 100644 index 51a153e..0000000 --- a/Lib/atexit.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -atexit.py - allow programmer to define multiple exit functions to be executed -upon normal program termination. - -One public function, register, is defined. -""" - -__all__ = ["register"] - -import sys - -_exithandlers = [] -def _run_exitfuncs(): - """run any registered exit functions - - _exithandlers is traversed in reverse order so functions are executed - last in, first out. - """ - - exc_info = None - while _exithandlers: - func, targs, kargs = _exithandlers.pop() - try: - func(*targs, **kargs) - except SystemExit: - exc_info = sys.exc_info() - except: - import traceback - print("Error in atexit._run_exitfuncs:", file=sys.stderr) - traceback.print_exc() - exc_info = sys.exc_info() - - if exc_info is not None: - raise exc_info[0], exc_info[1], exc_info[2] - - -def register(func, *targs, **kargs): - """register a function to be executed upon normal program termination - - func - function to be called at exit - targs - optional arguments to pass to func - kargs - optional keyword arguments to pass to func - - func is returned to facilitate usage as a decorator. - """ - _exithandlers.append((func, targs, kargs)) - return func - -if hasattr(sys, "exitfunc"): - # Assume it's another registered exit function - append it to our list - register(sys.exitfunc) -sys.exitfunc = _run_exitfuncs - -if __name__ == "__main__": - def x1(): - print("running x1") - def x2(n): - print("running x2(%r)" % (n,)) - def x3(n, kwd=None): - print("running x3(%r, kwd=%r)" % (n, kwd)) - - register(x1) - register(x2, 12) - register(x3, 5, "bar") - register(x3, "no kwd args") diff --git a/Lib/test/test___all__.py b/Lib/test/test___all__.py index d8e850a..bb1fd8d 100644 --- a/Lib/test/test___all__.py +++ b/Lib/test/test___all__.py @@ -48,7 +48,6 @@ class AllTest(unittest.TestCase): self.check_all("StringIO") self.check_all("UserString") self.check_all("aifc") - self.check_all("atexit") self.check_all("audiodev") self.check_all("base64") self.check_all("bdb") diff --git a/Lib/test/test_atexit.py b/Lib/test/test_atexit.py index 9d1e003..56077e7 100644 --- a/Lib/test/test_atexit.py +++ b/Lib/test/test_atexit.py @@ -4,97 +4,112 @@ import StringIO import atexit from test import test_support -class TestCase(unittest.TestCase): - def test_args(self): - # be sure args are handled properly - s = StringIO.StringIO() - sys.stdout = sys.stderr = s - save_handlers = atexit._exithandlers - atexit._exithandlers = [] - try: - atexit.register(self.h1) - atexit.register(self.h4) - atexit.register(self.h4, 4, kw="abc") - atexit._run_exitfuncs() - finally: - sys.stdout = sys.__stdout__ - sys.stderr = sys.__stderr__ - atexit._exithandlers = save_handlers - self.assertEqual(s.getvalue(), "h4 (4,) {'kw': 'abc'}\nh4 () {}\nh1\n") +### helpers +def h1(): + print("h1") - def test_order(self): - # be sure handlers are executed in reverse order - s = StringIO.StringIO() - sys.stdout = sys.stderr = s - save_handlers = atexit._exithandlers - atexit._exithandlers = [] - try: - atexit.register(self.h1) - atexit.register(self.h2) - atexit.register(self.h3) - atexit._run_exitfuncs() - finally: - sys.stdout = sys.__stdout__ - sys.stderr = sys.__stderr__ - atexit._exithandlers = save_handlers - self.assertEqual(s.getvalue(), "h3\nh2\nh1\n") +def h2(): + print("h2") - def test_sys_override(self): - # be sure a preset sys.exitfunc is handled properly - s = StringIO.StringIO() - sys.stdout = sys.stderr = s - save_handlers = atexit._exithandlers - atexit._exithandlers = [] - exfunc = sys.exitfunc - sys.exitfunc = self.h1 - reload(atexit) - try: - atexit.register(self.h2) - atexit._run_exitfuncs() - finally: - sys.stdout = sys.__stdout__ - sys.stderr = sys.__stderr__ - atexit._exithandlers = save_handlers - sys.exitfunc = exfunc - self.assertEqual(s.getvalue(), "h2\nh1\n") +def h3(): + print("h3") - def test_raise(self): - # be sure raises are handled properly - s = StringIO.StringIO() - sys.stdout = sys.stderr = s - save_handlers = atexit._exithandlers - atexit._exithandlers = [] - try: - atexit.register(self.raise1) - atexit.register(self.raise2) - self.assertRaises(TypeError, atexit._run_exitfuncs) - finally: - sys.stdout = sys.__stdout__ - sys.stderr = sys.__stderr__ - atexit._exithandlers = save_handlers +def h4(*args, **kwargs): + print("h4", args, kwargs) + +def raise1(): + raise TypeError - ### helpers - def h1(self): - print("h1") +def raise2(): + raise SystemError - def h2(self): - print("h2") +class TestCase(unittest.TestCase): + def setUp(self): + self.stream = StringIO.StringIO() + sys.stdout = sys.stderr = self.stream + atexit._clear() + + def tearDown(self): + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ + atexit._clear() - def h3(self): - print("h3") + def test_args(self): + # be sure args are handled properly + atexit.register(h1) + atexit.register(h4) + atexit.register(h4, 4, kw="abc") + atexit._run_exitfuncs() - def h4(self, *args, **kwargs): - print("h4", args, kwargs) + self.assertEqual(self.stream.getvalue(), + "h4 (4,) {'kw': 'abc'}\nh4 () {}\nh1\n") - def raise1(self): - raise TypeError + def test_order(self): + # be sure handlers are executed in reverse order + atexit.register(h1) + atexit.register(h2) + atexit.register(h3) + atexit._run_exitfuncs() + + self.assertEqual(self.stream.getvalue(), "h3\nh2\nh1\n") - def raise2(self): - raise SystemError + def test_raise(self): + # be sure raises are handled properly + atexit.register(raise1) + atexit.register(raise2) + + self.assertRaises(TypeError, atexit._run_exitfuncs) + + def test_stress(self): + a = [0] + def inc(): + a[0] += 1 + + for i in range(128): + atexit.register(inc) + atexit._run_exitfuncs() + + self.assertEqual(a[0], 128) + + def test_clear(self): + a = [0] + def inc(): + a[0] += 1 + + atexit.register(inc) + atexit._clear() + atexit._run_exitfuncs() + + self.assertEqual(a[0], 0) + + def test_unregister(self): + a = [0] + def inc(): + a[0] += 1 + def dec(): + a[0] -= 1 + + for i in range(4): + atexit.register(inc) + atexit.register(dec) + atexit.unregister(inc) + atexit._run_exitfuncs() + + self.assertEqual(a[0], -1) + + def test_bound_methods(self): + l = [] + atexit.register(l.append, 5) + atexit._run_exitfuncs() + self.assertEqual(l, [5]) + + atexit.unregister(l.append) + atexit._run_exitfuncs() + self.assertEqual(l, [5]) + def test_main(): test_support.run_unittest(TestCase) - if __name__ == "__main__": test_main() @@ -28,6 +28,9 @@ TO DO Core and Builtins ----------------- +- Patch #1680961: sys.exitfunc has been removed and replaced with a private + C-level API. + - PEP 3115: new metaclasses: the metaclass is now specified as a keyword arg in the class statement, which can now use the full syntax of a parameter list. Also, the metaclass can implement a __prepare__ function @@ -156,6 +159,8 @@ Extension Modules Library ------- +- Patch #1680961: atexit has been reimplemented in C. + - Removed all traces of the sets module. Build diff --git a/Modules/Setup.dist b/Modules/Setup.dist index 46f4253..173dfcc 100644 --- a/Modules/Setup.dist +++ b/Modules/Setup.dist @@ -176,6 +176,7 @@ GLHACK=-Dclear=__GLclear #collections collectionsmodule.c # Container types #itertools itertoolsmodule.c # Functions creating iterators for efficient looping #strop stropmodule.c # String manipulations +#atexit atexitmodule.c # Register functions to be run at interpreter-shutdown #unicodedata unicodedata.c # static Unicode character database diff --git a/Modules/atexitmodule.c b/Modules/atexitmodule.c new file mode 100644 index 0000000..b8a8bf6 --- /dev/null +++ b/Modules/atexitmodule.c @@ -0,0 +1,217 @@ +/* + * atexit - allow programmer to define multiple exit functions to be executed + * upon normal program termination. + * + * Translated from atexit.py by Collin Winter. + + Copyright 2007 Python Software Foundation. + */ + +#include "Python.h" + +/* ===================================================================== */ +/* Callback machinery. */ + +typedef struct { + PyObject *func; + PyObject *args; + PyObject *kwargs; +} atexit_callback; + +atexit_callback **atexit_callbacks; +int ncallbacks = 0; +int callback_len = 32; + +/* Installed into pythonrun.c's atexit mechanism */ + +void +atexit_callfuncs(void) +{ + PyObject *exc_type = NULL, *exc_value, *exc_tb, *r; + atexit_callback *cb; + int i; + + if (ncallbacks == 0) + return; + + for(i = ncallbacks - 1; i >= 0; i--) + { + cb = atexit_callbacks[i]; + if (cb == NULL) + continue; + + r = PyObject_Call(cb->func, cb->args, cb->kwargs); + Py_XDECREF(r); + if (r == NULL) { + if (exc_type) { + Py_DECREF(exc_type); + Py_DECREF(exc_value); + Py_DECREF(exc_tb); + } + PyErr_Fetch(&exc_type, &exc_value, &exc_tb); + if (!PyErr_ExceptionMatches(PyExc_SystemExit)) { + PySys_WriteStderr("Error in atexit._run_exitfuncs:\n"); + PyErr_Display(exc_type, exc_value, exc_tb); + } + } + } + + if (exc_type) + PyErr_Restore(exc_type, exc_value, exc_tb); +} + +void +atexit_delete_cb(int i) +{ + atexit_callback *cb = atexit_callbacks[i]; + atexit_callbacks[i] = NULL; + Py_DECREF(cb->func); + Py_DECREF(cb->args); + Py_XDECREF(cb->kwargs); + PyMem_Free(cb); +} + +/* ===================================================================== */ +/* Module methods. */ + +PyDoc_STRVAR(atexit_register__doc__, +"register(func, *args, **kwargs) -> func\n\ +\n\ +Register a function to be executed upon normal program termination\n\ +\n\ + func - function to be called at exit\n\ + args - optional arguments to pass to func\n\ + kwargs - optional keyword arguments to pass to func\n\ +\n\ + func is returned to facilitate usage as a decorator."); + +static PyObject * +atexit_register(PyObject *self, PyObject *args, PyObject *kwargs) +{ + atexit_callback *new_callback; + PyObject *func = NULL; + + if (ncallbacks >= callback_len) { + callback_len += 16; + atexit_callbacks = PyMem_Realloc(atexit_callbacks, + sizeof(atexit_callback*) * callback_len); + + } + + if (PyTuple_GET_SIZE(args) == 0) { + PyErr_SetString(PyExc_TypeError, + "register() takes at least 1 argument (0 given)"); + return NULL; + } + + func = PyTuple_GET_ITEM(args, 0); + if (!PyCallable_Check(func)) { + PyErr_SetString(PyExc_TypeError, + "the first argument must be callable"); + return NULL; + } + + new_callback = PyMem_Malloc(sizeof(atexit_callback)); + if (new_callback == NULL) + return PyErr_NoMemory(); + + new_callback->args = PyTuple_GetSlice(args, 1, PyTuple_GET_SIZE(args)); + if (new_callback->args == NULL) { + PyMem_Free(new_callback); + return NULL; + } + new_callback->func = func; + new_callback->kwargs = kwargs; + Py_INCREF(func); + Py_XINCREF(kwargs); + + atexit_callbacks[ncallbacks++] = new_callback; + + Py_INCREF(func); + return func; +} + +static PyObject * +atexit_run_exitfuncs(PyObject *self) +{ + atexit_callfuncs(); + if (PyErr_Occurred()) + return NULL; + Py_RETURN_NONE; +} + +static PyObject * +atexit_clear(PyObject *self) +{ + atexit_callback *cb; + int i; + + for(i = 0; i < ncallbacks; i++) + { + cb = atexit_callbacks[i]; + if (cb == NULL) + continue; + + atexit_delete_cb(i); + } + ncallbacks = 0; + Py_RETURN_NONE; +} + +static PyObject * +atexit_unregister(PyObject *self, PyObject *func) +{ + atexit_callback *cb; + int i, eq; + + for(i = 0; i < ncallbacks; i++) + { + cb = atexit_callbacks[i]; + if (cb == NULL) + continue; + + eq = PyObject_RichCompareBool(cb->func, func, Py_EQ); + if (eq < 0) + return NULL; + if (eq) + atexit_delete_cb(i); + } + Py_RETURN_NONE; +} + +static PyMethodDef atexit_methods[] = { + {"register", (PyCFunction) atexit_register, METH_VARARGS|METH_KEYWORDS, + atexit_register__doc__}, + {"_clear", (PyCFunction) atexit_clear, METH_NOARGS, + NULL}, + {"unregister", (PyCFunction) atexit_unregister, METH_O, + NULL}, + {"_run_exitfuncs", (PyCFunction) atexit_run_exitfuncs, METH_NOARGS, + NULL}, + {NULL, NULL} /* sentinel */ +}; + +/* ===================================================================== */ +/* Initialization function. */ + +PyDoc_STRVAR(atexit__doc__, +"atexit.py - allow programmer to define multiple exit functions to be executed\ +upon normal program termination.\n\ +\n\ +One public function, register, is defined.\n\ +"); + +PyMODINIT_FUNC +initatexit(void) +{ + PyObject *m; + + atexit_callbacks = PyMem_New(atexit_callback*, callback_len); + if (atexit_callbacks == NULL) + return; + + m = Py_InitModule3("atexit", atexit_methods, atexit__doc__); + if (m == NULL) + return; + + _Py_PyAtExit(atexit_callfuncs); +} diff --git a/Python/import.c b/Python/import.c index 33953c7..6d65703 100644 --- a/Python/import.c +++ b/Python/import.c @@ -361,7 +361,7 @@ PyImport_GetModuleDict(void) /* List of names to clear in sys */ static char* sys_deletes[] = { - "path", "argv", "ps1", "ps2", "exitfunc", + "path", "argv", "ps1", "ps2", "exc_type", "exc_value", "exc_traceback", "last_type", "last_value", "last_traceback", "path_hooks", "path_importer_cache", "meta_path", diff --git a/Python/pythonrun.c b/Python/pythonrun.c index d60ca9e..31c8329 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -56,7 +56,7 @@ static PyObject *run_pyc_file(FILE *, const char *, PyObject *, PyObject *, PyCompilerFlags *); static void err_input(perrdetail *); static void initsigs(void); -static void call_sys_exitfunc(void); +static void call_py_exitfuncs(void); static void call_ll_exitfuncs(void); extern void _PyUnicode_Init(void); extern void _PyUnicode_Fini(void); @@ -355,7 +355,7 @@ Py_Finalize(void) * threads created thru it, so this also protects pending imports in * the threads created via Threading. */ - call_sys_exitfunc(); + call_py_exitfuncs(); initialized = 0; /* Get current thread state and interpreter pointer */ @@ -1557,6 +1557,23 @@ Py_FatalError(const char *msg) #include "pythread.h" #endif +static void (*pyexitfunc)(void) = NULL; +/* For the atexit module. */ +void _Py_PyAtExit(void (*func)(void)) +{ + pyexitfunc = func; +} + +static void +call_py_exitfuncs(void) +{ + if (pyexitfunc == NULL) + return; + + (*pyexitfunc)(); + PyErr_Clear(); +} + #define NEXITFUNCS 32 static void (*exitfuncs[NEXITFUNCS])(void); static int nexitfuncs = 0; @@ -1570,27 +1587,6 @@ int Py_AtExit(void (*func)(void)) } static void -call_sys_exitfunc(void) -{ - PyObject *exitfunc = PySys_GetObject("exitfunc"); - - if (exitfunc) { - PyObject *res; - Py_INCREF(exitfunc); - PySys_SetObject("exitfunc", (PyObject *)NULL); - res = PyEval_CallObject(exitfunc, (PyObject *)NULL); - if (res == NULL) { - if (!PyErr_ExceptionMatches(PyExc_SystemExit)) { - PySys_WriteStderr("Error in sys.exitfunc:\n"); - } - PyErr_Print(); - } - Py_DECREF(exitfunc); - } - -} - -static void call_ll_exitfuncs(void) { while (nexitfuncs > 0) diff --git a/Python/sysmodule.c b/Python/sysmodule.c index c7d85933..d3c90bf 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -897,9 +897,6 @@ excepthook -- called to handle any uncaught exception other than SystemExit\n\ To customize printing in an interactive session or to install a custom\n\ top-level exception handler, assign other functions to replace these.\n\ \n\ -exitfunc -- if sys.exitfunc exists, this routine is called when Python exits\n\ - Assigning to sys.exitfunc is deprecated; use the atexit module instead.\n\ -\n\ stdin -- standard input file object; used by raw_input() and input()\n\ stdout -- standard output file object; used by print()\n\ stderr -- standard error object; used for error messages\n\ @@ -379,6 +379,8 @@ class PyBuildExt(build_ext): exts.append( Extension('operator', ['operator.c']) ) # _functools exts.append( Extension("_functools", ["_functoolsmodule.c"]) ) + # atexit + exts.append( Extension("atexit", ["atexitmodule.c"]) ) # Python C API test module exts.append( Extension('_testcapi', ['_testcapimodule.c']) ) # profilers (_lsprof is for cProfile.py) |