diff -r adc2f5003ea3 Lib/test/test_exceptions.py --- a/Lib/test/test_exceptions.py Sat Mar 29 06:06:52 2008 +0100 +++ b/Lib/test/test_exceptions.py Sat Mar 29 15:34:49 2008 +0100 @@ -4,6 +4,8 @@ import sys import sys import unittest import pickle +import threading +import weakref from test.test_support import TESTFN, unlink, run_unittest @@ -410,6 +412,54 @@ class ExceptionTests(unittest.TestCase): del e self.failIf('e' in locals()) + def testExceptionCleanupThreading(self): + # Make sure exception state is really cleaned up, even if we switch + # threads before destroying the frame where the exception was raised. + # See #2507 + + class MyException(Exception): + def __init__(self, obj): + self.obj = obj + class MyObj: + pass + + def inner_raising_func(): + # Create some references in exception value and traceback + local_ref = obj + raise MyException(obj) + + def raising_func(): + try: + inner_raising_func() + except MyException as e: + pass + with cond: + cond.notifyAll() + # Give back control to the main thread + with block: + pass + + obj = MyObj() + wr = weakref.ref(obj) + cond = threading.Condition() + cond.acquire() + block = threading.Lock() + + t = threading.Thread(target=raising_func) + try: + block.acquire() + t.start() + cond.wait() + # The last references to the instance shall now die + obj = None + obj = wr() + self.failUnless(obj is None, "%s" % obj) + finally: + cond.release() + # Endly, raising_func() can return and destroy its frame + block.release() + t.join(1.0) + def test_main(): run_unittest(ExceptionTests) diff -r adc2f5003ea3 Python/ceval.c --- a/Python/ceval.c Sat Mar 29 06:06:52 2008 +0100 +++ b/Python/ceval.c Sat Mar 29 15:34:49 2008 +0100 @@ -1477,6 +1477,18 @@ PyEval_EvalFrameEx(PyFrameObject *f, int "'finally' pops bad exception"); why = WHY_EXCEPTION; } + /* + Make sure the exception state is cleaned up, even if there + is a thread switch before we leave the frame. This ensures + references to the exception state are not kept too long. + See #2507. + */ + if (tstate->frame->f_exc_type != NULL) + reset_exc_info(tstate); + else { + assert(tstate->frame->f_exc_value == NULL); + assert(tstate->frame->f_exc_traceback == NULL); + } Py_DECREF(v); break;