Fix bug 1052242. Also includes rewrite of test case using unittest and
avoiding use of popen.
diff --git a/Doc/lib/libatexit.tex b/Doc/lib/libatexit.tex
index c9775d1..922f5d4 100644
--- a/Doc/lib/libatexit.tex
+++ b/Doc/lib/libatexit.tex
@@ -39,6 +39,12 @@
order. The assumption is that lower level modules will normally be
imported before higher level modules and thus must be cleaned up
later.
+
+If an exception is raised during execution of the exit handlers, a traceback
+is printed (unless SystemExit is raised) and the exception information is
+saved. After all exit handlers have had a chance to run the last exception
+to be raised is reraised.
+
\end{funcdesc}
diff --git a/Lib/atexit.py b/Lib/atexit.py
index 85ccb24..e109eb5 100644
--- a/Lib/atexit.py
+++ b/Lib/atexit.py
@@ -15,9 +15,22 @@
last in, first out.
"""
+ exc_info = None
while _exithandlers:
func, targs, kargs = _exithandlers.pop()
- func(*targs, **kargs)
+ try:
+ func(*targs, **kargs)
+ except SystemExit:
+ exc_info = sys.exc_info()
+ except:
+ import sys, traceback
+ print >> sys.stderr, "Error in atexit._run_exitfuncs:"
+ 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
@@ -33,7 +46,6 @@
# Assume it's another registered exit function - append it to our list
register(sys.exitfunc)
sys.exitfunc = _run_exitfuncs
-
del sys
if __name__ == "__main__":
diff --git a/Lib/test/test_atexit.py b/Lib/test/test_atexit.py
index 1d120df..e57c48a 100644
--- a/Lib/test/test_atexit.py
+++ b/Lib/test/test_atexit.py
@@ -1,66 +1,100 @@
-# Test the atexit module.
-from test.test_support import TESTFN, vereq, is_jython
-import atexit
-from os import popen, unlink
import sys
-
-executable = sys.executable
-if is_jython:
- executable = "jython"
-
-input = """\
+import unittest
+import StringIO
import atexit
+from test import test_support
-def handler1():
- print "handler1"
+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")
-def handler2(*args, **kargs):
- print "handler2", args, kargs
+ 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(handler1)
-atexit.register(handler2)
-atexit.register(handler2, 7, kw="abc")
-"""
+ 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")
-fname = TESTFN + ".py"
-f = file(fname, "w")
-f.write(input)
-f.close()
+ 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"
-p = popen('"%s" %s' % (executable, fname))
-output = p.read()
-p.close()
-vereq(output, """\
-handler2 (7,) {'kw': 'abc'}
-handler2 () {}
-handler1
-""")
+ def h2(self):
+ print "h2"
-input = """\
-def direct():
- print "direct exit"
+ def h3(self):
+ print "h3"
-import sys
-sys.exitfunc = direct
+ def h4(self, *args, **kwargs):
+ print "h4", args, kwargs
-# Make sure atexit doesn't drop
-def indirect():
- print "indirect exit"
+ def raise1(self):
+ raise TypeError
-import atexit
-atexit.register(indirect)
-"""
+ def raise2(self):
+ raise SystemError
-f = file(fname, "w")
-f.write(input)
-f.close()
+def test_main():
+ test_support.run_unittest(TestCase)
-p = popen('"%s" %s' % (executable, fname))
-output = p.read()
-p.close()
-vereq(output, """\
-indirect exit
-direct exit
-""")
-unlink(fname)
+if __name__ == "__main__":
+ test_main()
diff --git a/Misc/NEWS b/Misc/NEWS
index e2b1937..f44e10d 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -2,6 +2,17 @@
Python News
+++++++++++
+What's New in Python 2.4 release candidate 1?
+=============================================
+
+Library
+-------
+
+- Bug 1052242: If exceptions are raised by an atexit handler function an
+ attempt is made to execute the remaining handlers. The last exception
+ raised is re-raised.
+
+
(editors: check NEWS.help for information about editing NEWS using ReST.)
What's New in Python 2.4 beta 2?