Index: Python/pythonrun.c =================================================================== --- Python/pythonrun.c (revision 54403) +++ Python/pythonrun.c (working copy) @@ -56,7 +56,7 @@ 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 @@ * 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 */ @@ -489,8 +489,7 @@ if (Py_GETENV("PYTHONMALLOCSTATS")) _PyObject_DebugMallocStats(); #endif - - call_ll_exitfuncs(); + call_ll_exitfuncs(); } /* Create and initialize a new interpreter and thread, and return the @@ -1557,6 +1556,23 @@ #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 +1586,6 @@ } 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) Index: Python/import.c =================================================================== --- Python/import.c (revision 54403) +++ Python/import.c (working copy) @@ -360,7 +360,7 @@ /* 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", Index: Python/sysmodule.c =================================================================== --- Python/sysmodule.c (revision 54403) +++ Python/sysmodule.c (working copy) @@ -897,9 +897,6 @@ 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\ Index: Include/pythonrun.h =================================================================== --- Include/pythonrun.h (revision 54403) +++ Include/pythonrun.h (working copy) @@ -69,6 +69,10 @@ 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); Index: setup.py =================================================================== --- setup.py (revision 54403) +++ setup.py (working copy) @@ -379,6 +379,8 @@ 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) Index: Doc/lib/libatexit.tex =================================================================== --- Doc/lib/libatexit.tex (revision 54403) +++ Doc/lib/libatexit.tex (working copy) @@ -8,26 +8,14 @@ \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. +The \module{atexit} module defines functions to register and +unregister 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. +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. -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}. - \begin{funcdesc}{register}{func\optional{, *args\optional{, **kargs}}} Register \var{func} as a function to be executed at termination. Any optional arguments that are to be passed to \var{func} must be passed @@ -50,7 +38,16 @@ 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} + + \begin{seealso} \seemodule{readline}{Useful example of \module{atexit} to read and write \refmodule{readline} history files.} Index: Doc/lib/libsys.tex =================================================================== --- Doc/lib/libsys.tex (revision 54403) +++ Doc/lib/libsys.tex (working copy) @@ -218,19 +218,6 @@ 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()}. Index: Lib/atexit.py =================================================================== --- Lib/atexit.py (revision 54403) +++ Lib/atexit.py (working copy) @@ -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") Index: Lib/test/test_atexit.py =================================================================== --- Lib/test/test_atexit.py (revision 54403) +++ Lib/test/test_atexit.py (working copy) @@ -4,97 +4,101 @@ import atexit from test import test_support +### helpers +def h1(): + print("h1") + +def h2(): + print("h2") + +def h3(): + print("h3") + +def h4(*args, **kwargs): + print("h4", args, kwargs) + +def raise1(): + raise TypeError + +def raise2(): + raise SystemError + 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 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") + atexit.register(h1) + atexit.register(h4) + atexit.register(h4, 4, kw="abc") + atexit._run_exitfuncs() + self.assertEqual(self.stream.getvalue(), + "h4 (4,) {'kw': 'abc'}\nh4 () {}\nh1\n") + 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") + atexit.register(h1) + atexit.register(h2) + atexit.register(h3) + atexit._run_exitfuncs() + + self.assertEqual(self.stream.getvalue(), "h3\nh2\nh1\n") - 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 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 + 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) - ### helpers - def h1(self): - print("h1") - - def h2(self): - print("h2") - - def h3(self): - print("h3") - - def h4(self, *args, **kwargs): - print("h4", args, kwargs) - - def raise1(self): - raise TypeError - - def raise2(self): - raise SystemError - def test_main(): test_support.run_unittest(TestCase) - if __name__ == "__main__": test_main() Index: Lib/test/test___all__.py =================================================================== --- Lib/test/test___all__.py (revision 54403) +++ Lib/test/test___all__.py (working copy) @@ -48,7 +48,6 @@ 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") Index: Modules/atexitmodule.c =================================================================== --- Modules/atexitmodule.c (revision 0) +++ Modules/atexitmodule.c (revision 0) @@ -0,0 +1,215 @@ +/* + * 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; + + for(i = 0; i < ncallbacks; i++) + { + cb = atexit_callbacks[i]; + if (cb == NULL) + continue; + + if (cb->func == func) { + 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); +} Property changes on: Modules/atexitmodule.c ___________________________________________________________________ Name: svn:keywords + Id Name: svn:eol-style + native Index: Modules/Setup.dist =================================================================== --- Modules/Setup.dist (revision 54403) +++ Modules/Setup.dist (working copy) @@ -176,6 +176,7 @@ #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