-
-
Notifications
You must be signed in to change notification settings - Fork 30.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
nested try..excepts don't work correctly for generators #69798
Comments
Nested try..except statements with yields can loose reference to the current exception. The following code: class MainError(Exception):
pass
class SubError(Exception):
pass
def main():
try:
raise MainError()
except MainError:
try:
yield
except SubError:
print('got SubError')
raise
coro = main()
coro.send(None)
coro.throw(SubError()) prints: got SubError
Traceback (most recent call last):
File "t.py", line 19, in <module>
coro.throw(SubError())
File "t.py", line 15, in main
raise
RuntimeError: No active exception to reraise |
This was originally discovered here: python/asyncio#287 |
Is this issue related to the issue bpo-23353? |
I reverted the change of the issue bpo-23353 but it doesn't fix this example, so it looks like these issues are not related. Cool. |
Another bug: class MainError(Exception):
pass
class SubError(Exception):
pass
def main():
try:
raise MainError()
except MainError:
yield
coro = main()
coro.send(None)
coro.throw(SubError()) SubError will propagate, but won't have MainError in its __context__ |
Here's a patch the fixes the first problem (but __context__ bug is still open). I'm not sure that the patch is correct :( But at least I've added new unittests (one still failing) |
Can someone work with me on fixing this issue? I think it's important to ship 3.5.1 with this resolved (and 3.4.4 FWIW). |
You might have to ping python-dev. But in terms of priorities I think |
Regarding the second bug, did you consider that the exception thrown to the generator can already have __context__ set? try: I guess either one context could trump the other, or we could to follow the chain of contexts and append the other chain at the end. Both these ideas seem a bit ugly though. |
It don't consider this issue as a blocker for Python 3.5.1. This |
Thinking about the __context__ thing some more, I guess it might make sense for __context__ to be overwritten by the generator context. The same way it gets overwritten when you execute “raise” inside an exception handler. |
Not sure I understand what you're saying here. |
I was making an analogy between how the “raise” statement works, and how the throw() method could work. In this example, there are three exception objects (MainError, SubError, and ValueError). I was suggesting that it is okay for the context to be set to the MainError instance, because that is how the analogous version using a “raise” statement works. def main():
try:
raise MainError("Context inside generator")
except MainError:
yield # __context__ could be changed to MainError
coro = main()
coro.send(None)
try:
try: raise ValueError("Context outside of generator")
except ValueError: raise SubError()
except SubError as ex:
coro.throw(ex) # __context__ is ValueError # raise analogy: try: try: === Sorry I’m not really familiar with the code to quickly propose a patch or review your change though. |
I don't plan to hold up 3.5.1 for this. |
Martin, you might be right here. Guido, do you think it's OK that SubError doesn't have MainError in its __context__? http://bugs.python.org/issue25612#msg254576 |
I'm sorry, I'm no help here -- I don't know how __context__ is On Fri, Nov 20, 2015 at 8:14 AM, Yury Selivanov <report@bugs.python.org> wrote:
|
Anyways, here's a separate issue for the __context__ problem: http://bugs.python.org/issue25683 |
Guido, Martin, I've just posted to python-dev about this ticket. Let's see what others think. |
Interrogating the thread state like that makes me wonder how this patch behaves in the following scenario: class MainError(Exception): pass
class SubError(Exception): pass
def yield_coro():
yield
coro = yield_coro()
coro.send(None)
Also potentially worth exploring is this proposed architectural change from Mark Shannon to move all exception related state out of frame objects: http://bugs.python.org/issue13897 While we couldn't do the latter in a maintenance release, I'd be interested to know what effect it has on this edge case. |
Nick, your scenario seems to behave no differently with and without the patch: Traceback (most recent call last):
File "<stdin>", line 2, in <module>
__main__.MainError |
I closed issue bpo-25683 as "not a bug". So it's just this issue that we need to fix. Anyone wants to review the patch? :) Since the code involved here is quite complex, I don't want to commit this patch without a thorough review. |
I read the patch but I don't understand the logic behind it :-). Why should the value of tstate->exc_type affect whether we save/restore exc_info? Won't this mean that things are still broken for code like: ----- def f():
try:
raise KeyError
except Exception:
try:
yield
except Exception:
pass
raise try: ? Conceptually I would have thought the fix would be to remove the '!throwflag' check, but I haven't actually tried it... |
(bpo-29587 is a duplicate of this one, but it has some more information on where the !throwflag check came from and why it might be possible to remove it now.) |
I tried the following code: def g():
yield 1
raise
yield 2
i = g()
try:
1/0
except:
next(i)
next(i) Currently it raises: Traceback (most recent call last):
File "<stdin>", line 5, in <module>
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero With PR 1773 applied it raises: Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 5, in <module>
File "<stdin>", line 3, in g
RuntimeError: No active exception to reraise And this looks more correct. But if replace raise with print(sys.exc_info()) the patched and unpatched interpreters both print: (<class 'ZeroDivisionError'>, ZeroDivisionError('division by zero',), <traceback object at 0x7f61d9ed1448>) Is it correct that sys.exc_info() return an exception while raise can't reraise it? |
Thanks Serhiy for spotting that. 'raise' should raise the same exception as sys.exc_info() returns. |
Thank you Mark for the fix! |
Note that removing exc_type, exc_value and exc_traceback from PyThreadState breaks Cython. |
The problem I mentioned in msg304117 has been resolved in backward direction: "raise" outside of an except block don't raise a RuntimeError. |
Looking at the docs: https://docs.python.org/3.6/library/sys.html#sys.exc_info states: And https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement If no expressions are present, raise re-raises the last exception that was active in the current scope. If no exception is active in the current scope, a RuntimeError exception is raised indicating that this is an error. Note that Which means that the newly implemented behaviour is correct.
Is there a matching Cython issue? |
Thank you for the clarification Mark. I agree that the current behavior is well justified. Cython was fixed in cython/cython@2d33924. |
P.S. I added a very minimal What's New in 3.7 entry for this change since it has affected some downline projects. I just copied the NEWS entry. Feel free to expand it and/or move it to a better location in the file. |
This change was in top5 breaking changes for 3.7. It broke many projects that use Cython until they upgraded Cython to the version that supports 3.7. |
It seems like this issue introduced a regression in Python 3.7.0: bpo-33996 "Crash in gen_send_ex(): _PyErr_GetTopmostException() returns freed memory". |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: