Author njs
Recipients njs
Date 2017-02-17.11:01:48
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1487329309.81.0.182915384821.issue29587@psf.upfronthosting.co.za>
In-reply-to
Content
Example 1:

-----------
def f():
    try:
        raise KeyError
    except Exception:
        yield

gen = f()
gen.send(None)
gen.throw(ValueError)
---------

Output:
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in f
ValueError

Expected output:

Something involving the string "During handling of the above exception, another exception occurred", and a traceback for the original KeyError.


Example 2:

-----------
import sys

def f():
    try:
        raise KeyError
    except Exception:
        print(sys.exc_info())
        try:
            yield
        except Exception:
            pass
        print(sys.exc_info())
        raise

gen = f()
gen.send(None)
gen.throw(ValueError)
-----------

Output:

(<class 'KeyError'>, KeyError(), <traceback object at 0x7f67ce3c3f88>)
(None, None, None)
Traceback (most recent call last):
  File "/tmp/foo.py", line 17, in <module>
    gen.throw(ValueError)
  File "/tmp/foo.py", line 13, in f
    raise
RuntimeError: No active exception to reraise

Expected output: certainly not that :-)


This seems to happen because normally, generators save the current exc_info when yielding, and then restore it when re-entered. But, if we re-enter through 'throw' (throwflag is true), this is disabled:

https://github.com/python/cpython/blob/b2ee40ed9c9041dcff9c898aa19aacf9ec60308a/Python/ceval.c#L1027

This check seems to have been added in ae5f2f4a39e6a3f4c45e9dc95bd4e1fe5dfb60f2 as a fix for:

https://bugs.python.org/issue7173

which had to do with a nasty situation involving a generator object that  was part of a reference cycle: the gc sometimes would free the objects stored in the generator's saved exc_info, and then try to clean up the generator by throwing in a GeneratorExit.

AFAICT this situation shouldn't be possible anymore thanks to PEP 442, which makes it so that finalizers are run before any part of the cycle is freed. And in any case it certainly doesn't justify breaking code like the examples above.

(Note: the examples use generators for simplicity, but of course the way I noticed this was that I had some async/await code where exceptions were mysteriously disappearing instead of showing up in __context__ and couldn't figure out why. It's likely that more people will run into this in the future as async/await becomes more widely used. As a workaround for now I'll probably modify my coroutine runner so that it never uses 'throw'.)
History
Date User Action Args
2017-02-17 11:01:49njssetrecipients: + njs
2017-02-17 11:01:49njssetmessageid: <1487329309.81.0.182915384821.issue29587@psf.upfronthosting.co.za>
2017-02-17 11:01:49njslinkissue29587 messages
2017-02-17 11:01:48njscreate