Index: Python/frozenmain.c =================================================================== --- Python/frozenmain.c (revision 59851) +++ Python/frozenmain.c (working copy) @@ -9,6 +9,9 @@ extern int PyInitFrozenExtensions(void); #endif +/* keep track of failed atexit callbacks */ +extern int Py_FailedExitfuncCount; + /* Main program */ int @@ -64,5 +67,9 @@ PyWinFreeze_ExeTerm(); #endif Py_Finalize(); + + if(Py_FailedExitfuncCount > 0) + sts = 128 + Py_FailedExitfuncCount; + return sts; } Index: Python/pythonrun.c =================================================================== --- Python/pythonrun.c (revision 59851) +++ Python/pythonrun.c (working copy) @@ -82,6 +82,9 @@ true divisions (which they will be in 2.3). */ int _Py_QnewFlag = 0; +/* The number of failed exit callbacks*/ +int Py_FailedExitfuncCount = 0; + /* Reference to 'warnings' module, to avoid importing it on the fly when the import lock may be held. See 683658/771097 */ @@ -331,6 +334,29 @@ extern void dump_counts(FILE*); #endif +/* get the number of failed callbacks in atexit._run_callbacks */ +static int +count_failed_callbacks() +{ + PyObject *module; + PyObject *value; + int count; + + module = PyImport_ImportModule("atexit"); + if(module != NULL) { + value = PyObject_GetAttrString(module,"_failed_callbacks"); + Py_DECREF(module); + if(value != NULL) { + count = (int)PyInt_AsLong(value); + Py_DECREF(value); + return count; + } + } + + return -1; +}; + + /* Undo the effect of Py_Initialize(). Beware: if multiple interpreter and/or thread states exist, these @@ -364,6 +390,17 @@ * the threads created via Threading. */ call_sys_exitfunc(); + + /* Count the number of callbacks that failed while executing + * atexit._run_exitfuncs(). We only count if Py_FailedExitfuncCount is + * 0 AFTER the call_sys_exitfunc call, because if it's non-zero we + * know that the atexit module was not imported. and there is no point + * in counting. + */ + if(Py_FailedExitfuncCount == 0) + /*This only counts the callbacks registred through the atexit module*/ + Py_FailedExitfuncCount = count_failed_callbacks(); + initialized = 0; /* Get current thread state and interpreter pointer */ @@ -1605,17 +1642,38 @@ static void call_sys_exitfunc(void) { - PyObject *exitfunc = PySys_GetObject("exitfunc"); + PyObject *modules; + PyObject *exitfunc; + int is_imported = 0; + + /* We first check to see if the atexit module has been + * imported, if it hasn't we know that we are dealing with + * a single exit callback. */ + modules = PySys_GetObject("modules"); + if(modules) { + PyObject *atexit_key; + Py_INCREF(modules); + atexit_key = PyString_FromFormat("%s","atexit"); + is_imported = PyDict_Contains(modules,atexit_key); + + Py_DECREF(modules); + Py_DECREF(atexit_key); + } + + 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 (res == NULL && is_imported == 0) { if (!PyErr_ExceptionMatches(PyExc_SystemExit)) { PySys_WriteStderr("Error in sys.exitfunc:\n"); + } + Py_FailedExitfuncCount++; PyErr_Print(); } Py_DECREF(exitfunc); @@ -1639,6 +1697,8 @@ Py_Exit(int sts) { Py_Finalize(); + if(Py_FailedExitfuncCount > 0) + sts = 128 + Py_FailedExitfuncCount; exit(sts); } Index: Doc/library/atexit.rst =================================================================== --- Doc/library/atexit.rst (revision 59851) +++ Doc/library/atexit.rst (working copy) @@ -23,6 +23,12 @@ This is an alternate interface to the functionality provided by the ``sys.exitfunc`` variable. + +.. versionadded:: 2.6 + If exceptions are raised in the exit callbacks python will + exit with the code 128 + the number of exceptions raised. + + Note: This module is unlikely to work correctly when used with other code that sets ``sys.exitfunc``. In particular, other core Python modules are free to use :mod:`atexit` without the programmer's knowledge. Authors who use Index: Lib/atexit.py =================================================================== --- Lib/atexit.py (revision 59851) +++ Lib/atexit.py (working copy) @@ -9,6 +9,7 @@ import sys +_failed_callbacks=0 _exithandlers = [] def _run_exitfuncs(): """run any registered exit functions @@ -16,6 +17,7 @@ _exithandlers is traversed in reverse order so functions are executed last in, first out. """ + global _failed_callbacks exc_info = None while _exithandlers: @@ -26,6 +28,7 @@ exc_info = sys.exc_info() except: import traceback + _failed_callbacks+=1 print >> sys.stderr, "Error in atexit._run_exitfuncs:" traceback.print_exc() exc_info = sys.exc_info() Index: Lib/test/test_atexit.py =================================================================== --- Lib/test/test_atexit.py (revision 59851) +++ Lib/test/test_atexit.py (working copy) @@ -2,6 +2,7 @@ import unittest import StringIO import atexit +import subprocess from test import test_support class TestCase(unittest.TestCase): @@ -92,9 +93,64 @@ def raise2(self): raise SystemError +### +# +# test exit codes +# + +def _execute_script(script): + res = subprocess.call([sys.executable, "-c", script], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + return res + +class TestAtexitExitCode(unittest.TestCase): + def test_succeed(self): + script = """\ + +import atexit +def succeed(): + return + +atexit.register(succeed) + +""" + self.assertEqual(0, _execute_script(script)) + +### + + def test_failure_1(self): + script = """\ + +import atexit +def fail_1(): + raise Exception("atexit failure test") + +atexit.register(fail_1) + +""" + self.assertEqual(128 + 1, _execute_script(script)) + +### + + def test_failure_2(self): + script = """\ + +import atexit +def fail_1(): + raise Exception("atexit failure test") +def fail_2(): + raise Exception("another atexit failure test") +atexit.register(fail_1) +atexit.register(fail_2) + +""" + self.assertEqual(128 + 2, _execute_script(script)) + +### + def test_main(): - test_support.run_unittest(TestCase) + test_support.run_unittest(TestCase, TestAtexitExitCode) - if __name__ == "__main__": test_main() Index: Modules/main.c =================================================================== --- Modules/main.c (revision 59851) +++ Modules/main.c (working copy) @@ -52,6 +52,9 @@ extern int Py_RISCOSWimpFlag; #endif /*RISCOS*/ +/* Keep track of failed atexit callbacks. */ +extern Py_FailedExitfuncCount; + /* Short usage message (with %s for argv0) */ static char *usage_line = "usage: %s [option] ... [-c cmd | -m mod | file | -] [arg] ...\n"; @@ -616,6 +619,9 @@ _Py_ReleaseInternedStrings(); #endif /* __INSURE__ */ + if(Py_FailedExitfuncCount > 0) + sts = 128 + Py_FailedExitfuncCount; + return sts; }