New submission from Nathaniel Smith:

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'.)

----------
messages: 287987
nosy: njs
priority: normal
severity: normal
status: open
title: Generator/coroutine 'throw' discards exc_info state, which is bad
versions: Python 3.5, Python 3.6, Python 3.7

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue29587>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to