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.

This commit is contained in:
Collin Winter 2007-03-21 02:57:17 +00:00
parent 450ee81b22
commit 670e692134
13 changed files with 360 additions and 204 deletions

View File

@ -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}

View File

@ -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()}.

View File

@ -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);

View File

@ -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")

View File

@ -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")

View File

@ -4,97 +4,112 @@ import StringIO
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")
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")
atexit.register(h1)
atexit.register(h2)
atexit.register(h3)
atexit._run_exitfuncs()
self.assertEqual(self.stream.getvalue(), "h3\nh2\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
### 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
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()

View File

@ -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

View File

@ -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

217
Modules/atexitmodule.c Normal file
View File

@ -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);
}

View File

@ -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",

View File

@ -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;
@ -1569,27 +1586,6 @@ int Py_AtExit(void (*func)(void))
return 0;
}
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)
{

View File

@ -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\

View File

@ -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)