Commit 670e6921 authored by Collin Winter's avatar Collin Winter

Patch #1680961: remove sys.exitfunc and replace it with a private C API. Also,...

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.
parent 450ee81b
\section{\module{atexit} --- \section{\module{atexit} ---
Exit handlers} Exit handlers}
\declaremodule{standard}{atexit} \declaremodule{builtin}{atexit}
\moduleauthor{Skip Montanaro}{skip@mojam.com} \moduleauthor{Skip Montanaro}{skip@mojam.com}
\sectionauthor{Skip Montanaro}{skip@mojam.com} \sectionauthor{Skip Montanaro}{skip@mojam.com}
\modulesynopsis{Register and execute cleanup functions.} \modulesynopsis{Register and execute cleanup functions.}
\versionadded{2.0} \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 The \module{atexit} module defines functions to register and
signal, when a Python fatal internal error is detected, or when unregister cleanup functions. Functions thus registered are
\function{os._exit()} is called. automatically executed upon normal interpreter termination.
This is an alternate interface to the functionality provided by the Note: the functions registered via this module are not called when
\code{sys.exitfunc} variable. the program is killed by a signal, when a Python fatal internal
\withsubitem{(in sys)}{\ttindex{exitfunc}} error is detected, or when \function{os._exit()} is called.
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}}} \begin{funcdesc}{register}{func\optional{, *args\optional{, **kargs}}}
Register \var{func} as a function to be executed at termination. Any 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. ...@@ -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 \versionchanged[This function now returns \var{func} which makes it
possible to use it as a decorator without binding the 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} \end{funcdesc}
......
...@@ -218,19 +218,6 @@ It is always available. ...@@ -218,19 +218,6 @@ It is always available.
program when an error occurs. program when an error occurs.
\end{funcdesc} \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}{} \begin{funcdesc}{getcheckinterval}{}
Return the interpreter's ``check interval''; Return the interpreter's ``check interval'';
see \function{setcheckinterval()}. see \function{setcheckinterval()}.
......
...@@ -69,6 +69,10 @@ PyAPI_FUNC(void) PyErr_Print(void); ...@@ -69,6 +69,10 @@ PyAPI_FUNC(void) PyErr_Print(void);
PyAPI_FUNC(void) PyErr_PrintEx(int); PyAPI_FUNC(void) PyErr_PrintEx(int);
PyAPI_FUNC(void) PyErr_Display(PyObject *, PyObject *, PyObject *); 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(int) Py_AtExit(void (*func)(void));
PyAPI_FUNC(void) Py_Exit(int); PyAPI_FUNC(void) Py_Exit(int);
......
"""
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")
...@@ -48,7 +48,6 @@ class AllTest(unittest.TestCase): ...@@ -48,7 +48,6 @@ class AllTest(unittest.TestCase):
self.check_all("StringIO") self.check_all("StringIO")
self.check_all("UserString") self.check_all("UserString")
self.check_all("aifc") self.check_all("aifc")
self.check_all("atexit")
self.check_all("audiodev") self.check_all("audiodev")
self.check_all("base64") self.check_all("base64")
self.check_all("bdb") self.check_all("bdb")
......
...@@ -4,97 +4,112 @@ import StringIO ...@@ -4,97 +4,112 @@ import StringIO
import atexit import atexit
from test import test_support from test import test_support
class TestCase(unittest.TestCase): ### helpers
def test_args(self): def h1():
# be sure args are handled properly print("h1")
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")
def test_order(self): def h2():
# be sure handlers are executed in reverse order print("h2")
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): def h3():
# be sure a preset sys.exitfunc is handled properly print("h3")
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): def h4(*args, **kwargs):
# be sure raises are handled properly print("h4", args, kwargs)
s = StringIO.StringIO()
sys.stdout = sys.stderr = s def raise1():
save_handlers = atexit._exithandlers raise TypeError
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 raise2():
def h1(self): raise SystemError
print("h1")
def h2(self): class TestCase(unittest.TestCase):
print("h2") 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): def test_args(self):
print("h3") # 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): self.assertEqual(self.stream.getvalue(),
print("h4", args, kwargs) "h4 (4,) {'kw': 'abc'}\nh4 () {}\nh1\n")
def raise1(self): def test_order(self):
raise TypeError # 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): def test_raise(self):
raise SystemError # 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(): def test_main():
test_support.run_unittest(TestCase) test_support.run_unittest(TestCase)
if __name__ == "__main__": if __name__ == "__main__":
test_main() test_main()
...@@ -28,6 +28,9 @@ TO DO ...@@ -28,6 +28,9 @@ TO DO
Core and Builtins 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 - 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 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 a parameter list. Also, the metaclass can implement a __prepare__ function
...@@ -156,6 +159,8 @@ Extension Modules ...@@ -156,6 +159,8 @@ Extension Modules
Library Library
------- -------
- Patch #1680961: atexit has been reimplemented in C.
- Removed all traces of the sets module. - Removed all traces of the sets module.
Build Build
......
...@@ -176,6 +176,7 @@ GLHACK=-Dclear=__GLclear ...@@ -176,6 +176,7 @@ GLHACK=-Dclear=__GLclear
#collections collectionsmodule.c # Container types #collections collectionsmodule.c # Container types
#itertools itertoolsmodule.c # Functions creating iterators for efficient looping #itertools itertoolsmodule.c # Functions creating iterators for efficient looping
#strop stropmodule.c # String manipulations #strop stropmodule.c # String manipulations
#atexit atexitmodule.c # Register functions to be run at interpreter-shutdown
#unicodedata unicodedata.c # static Unicode character database #unicodedata unicodedata.c # static Unicode character database
......
/*
* 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);
}
...@@ -361,7 +361,7 @@ PyImport_GetModuleDict(void) ...@@ -361,7 +361,7 @@ PyImport_GetModuleDict(void)
/* List of names to clear in sys */ /* List of names to clear in sys */
static char* sys_deletes[] = { static char* sys_deletes[] = {
"path", "argv", "ps1", "ps2", "exitfunc", "path", "argv", "ps1", "ps2",
"exc_type", "exc_value", "exc_traceback", "exc_type", "exc_value", "exc_traceback",
"last_type", "last_value", "last_traceback", "last_type", "last_value", "last_traceback",
"path_hooks", "path_importer_cache", "meta_path", "path_hooks", "path_importer_cache", "meta_path",
......
...@@ -56,7 +56,7 @@ static PyObject *run_pyc_file(FILE *, const char *, PyObject *, PyObject *, ...@@ -56,7 +56,7 @@ static PyObject *run_pyc_file(FILE *, const char *, PyObject *, PyObject *,
PyCompilerFlags *); PyCompilerFlags *);
static void err_input(perrdetail *); static void err_input(perrdetail *);
static void initsigs(void); static void initsigs(void);
static void call_sys_exitfunc(void); static void call_py_exitfuncs(void);
static void call_ll_exitfuncs(void); static void call_ll_exitfuncs(void);
extern void _PyUnicode_Init(void); extern void _PyUnicode_Init(void);
extern void _PyUnicode_Fini(void); extern void _PyUnicode_Fini(void);
...@@ -355,7 +355,7 @@ Py_Finalize(void) ...@@ -355,7 +355,7 @@ Py_Finalize(void)
* threads created thru it, so this also protects pending imports in * threads created thru it, so this also protects pending imports in
* the threads created via Threading. * the threads created via Threading.
*/ */
call_sys_exitfunc(); call_py_exitfuncs();
initialized = 0; initialized = 0;
/* Get current thread state and interpreter pointer */ /* Get current thread state and interpreter pointer */
...@@ -1557,6 +1557,23 @@ Py_FatalError(const char *msg) ...@@ -1557,6 +1557,23 @@ Py_FatalError(const char *msg)
#include "pythread.h" #include "pythread.h"
#endif #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 #define NEXITFUNCS 32
static void (*exitfuncs[NEXITFUNCS])(void); static void (*exitfuncs[NEXITFUNCS])(void);
static int nexitfuncs = 0; static int nexitfuncs = 0;
...@@ -1569,27 +1586,6 @@ int Py_AtExit(void (*func)(void)) ...@@ -1569,27 +1586,6 @@ int Py_AtExit(void (*func)(void))
return 0; 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 static void
call_ll_exitfuncs(void) call_ll_exitfuncs(void)
{ {
......
...@@ -897,9 +897,6 @@ excepthook -- called to handle any uncaught exception other than SystemExit\n\ ...@@ -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\ To customize printing in an interactive session or to install a custom\n\
top-level exception handler, assign other functions to replace these.\n\ top-level exception handler, assign other functions to replace these.\n\
\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\ stdin -- standard input file object; used by raw_input() and input()\n\
stdout -- standard output file object; used by print()\n\ stdout -- standard output file object; used by print()\n\
stderr -- standard error object; used for error messages\n\ stderr -- standard error object; used for error messages\n\
......
...@@ -379,6 +379,8 @@ class PyBuildExt(build_ext): ...@@ -379,6 +379,8 @@ class PyBuildExt(build_ext):
exts.append( Extension('operator', ['operator.c']) ) exts.append( Extension('operator', ['operator.c']) )
# _functools # _functools
exts.append( Extension("_functools", ["_functoolsmodule.c"]) ) exts.append( Extension("_functools", ["_functoolsmodule.c"]) )
# atexit
exts.append( Extension("atexit", ["atexitmodule.c"]) )
# Python C API test module # Python C API test module
exts.append( Extension('_testcapi', ['_testcapimodule.c']) ) exts.append( Extension('_testcapi', ['_testcapimodule.c']) )
# profilers (_lsprof is for cProfile.py) # profilers (_lsprof is for cProfile.py)
......
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