Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

gh-111375: Fix handling of exceptions within@contextmanager-decorated functions#111676

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
pR0Ps wants to merge10 commits intopython:main
base:main
Choose a base branch
Loading
frompR0Ps:bugfix/with-statement-exception-handling

Conversation

pR0Ps
Copy link
Contributor

@pR0PspR0Ps commentedNov 3, 2023
edited by bedevere-appbot
Loading

Previously, when using atry...yield...except construct within a@contextmanager function, an exception raised by theyield wouldn't be properly cleared when handled by theexcept block. Instead, callingsys.exception() would continue to return the exception, leading to confusing tracebacks and logs.

This was happening due to the way that the@contextmanager decorator drives its decorated generator function. When an exception occurs, it uses.throw(exc) to throw the exception into the generator. However, even if the generator handles this exception, because the exception was thrown into it in the context of the@contextmanager decorator handling it (and not being finished yet),sys.exception() was not being reset.

For an example of this and more information, see#111375

In order to fix this, the exception context as the@contextmanager decorator is__enter__'d is stored and set as the current exception context just before throwing the new exception into the generator. Doing this means that after the generator handles the thrown exception,sys.exception() reverts back to what it was when the@contextmanager function was started.

Potential reviewers based on activity in the linked issue:@iritkatriel@ericsnowcurrently@ncoghlan

Copy link
Member

@ericsnowcurrentlyericsnowcurrently left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thanks for working on this. Mostly your solution fundamentally seems correct and specifically a good approach. I just have concerns about 2 points:

  • relying on ctypes
  • deletingself.exc_context instead of setting it back toNone

@bedevere-app
Copy link

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phraseI have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@iritkatriel
Copy link
Member

The function_PyErr_GetTopmostException skips overNones, and returns the first proper exception in the chain. I don't know what could get confused if we start setting the exception to None.

@pR0PspR0Psforce-pushed thebugfix/with-statement-exception-handling branch 2 times, most recently from582be48 to79766c8CompareNovember 4, 2023 03:54
@pR0Ps
Copy link
ContributorAuthor

pR0Ps commentedNov 4, 2023
edited
Loading

@iritkatriel

The function_PyErr_GetTopmostException skips overNones, and returns the first proper exception in the chain. I don't know what could get confused if we start setting the exception to None.

Hmm, yeah, now that you mention it, I don't know why this is currently working. Consider:

importsystry:raiseTypeError()exceptTypeError:try:raiseValueError()except:assertisinstance(sys.exception(),ValueError)sys._set_exception(None)# !!!assertsys.exception()isNone# !!!assertisinstance(sys.exception(),TypeError)

This works (using the latest commit on this branch), yet itdoes look like it shouldn't. It seems likesys._set_exception(None) should settstate->exc_info->exc_value toPy_None and therefore should makePyErr_GetTopmostException skip over it and get theTypeError instead whenassert sys.exception() is None runs.

Unless I'm misunderstanding something?

@pR0PspR0Psforce-pushed thebugfix/with-statement-exception-handling branch from79766c8 tob115754CompareNovember 4, 2023 04:34
@iritkatriel
Copy link
Member

@iritkatriel

The function_PyErr_GetTopmostException skips overNones, and returns the first proper exception in the chain. I don't know what could get confused if we start setting the exception to None.

Hmm, yeah, now that you mention it, I don't know why this is currently working. Consider:

importsystry:raiseTypeError()exceptTypeError:try:raiseValueError()except:assertisinstance(sys.exception(),ValueError)sys._set_exception(None)# !!!assertsys.exception()isNone# !!!assertisinstance(sys.exception(),TypeError)

This works (using the latest commit on this branch), yet itdoes look like it shouldn't. It seems likesys._set_exception(None) should settstate->exc_info->exc_value toPy_None and therefore should makePyErr_GetTopmostException skip over it and get theTypeError instead whenassert sys.exception() is None runs.

Unless I'm misunderstanding something?

Try nesting it in yet another try-except?

@pR0Ps
Copy link
ContributorAuthor

pR0Ps commentedNov 5, 2023
edited
Loading

Try nesting it in yet another try-except?

I have not been able to break it, no matter how much nesting above or below is used. This also runs without asserting:

importsystry:raiseRuntimeError()except:try:raiseTypeError()except:try:raiseValueError()except:asserttype(sys.exception())==ValueErrorasserttype(sys.exception().__context__)==TypeErrorasserttype(sys.exception().__context__.__context__)==RuntimeErrorsys._set_exception(None)# !!!assertsys.exception()isNone# !!!try:raiseIndexError()except:asserttype(sys.exception())==IndexErrorassertsys.exception().__context__isNonetry:raiseSyntaxError()except:asserttype(sys.exception())==SyntaxErrorasserttype(sys.exception().__context__)==IndexErrorassertsys.exception().__context__.__context__isNoneasserttype(sys.exception())==IndexErrorassertsys.exception().__context__isNoneassertsys.exception()isNone# !!!asserttype(sys.exception())==TypeErrorasserttype(sys.exception().__context__)==RuntimeErrorassertisinstance(sys.exception(),RuntimeError)assertsys.exception()isNone

@pR0Ps
Copy link
ContributorAuthor

pR0Ps commentedNov 6, 2023
edited
Loading

The piece I was missing was thatexc_info->previous_item is alwaysNULL except if there are generators/coroutines involved.

The following shows the issue@iritkatriel raised (I've added the state of theexc_info stack as comments):

importsysdefgen():# exc_info: Noneyield# exc_info: NULL --> ZeroDivisionError('division by zero')asserttype(sys.exception())==ZeroDivisionErrorsys._set_exception(None)# exc_info: None --> ZeroDivisionError('division by zero')assertsys.exception()isNone# 💣💣💣yieldg=gen()next(g)try:1/0except:next(g)

This breaks because generators push a newexc_info onto thetstate->exc_info stack. BecausePyErr_GetTopmostException skips overNULL/Noneexc_values,sys.exception() still returns theZeroDivisionError. In this state,sys._set_exception(None) is essentially a no-op since the top-levelexc_info->exc_value already wasNULL/None. The assert then fails on the next line.

This maybe brings up another "is it a bug or just a documentation issue" question (expand me)

InDoc/c-api/exceptions.rstPyErr_SetHandledException says the following:

To clear the exception state, passNULL.

and

it can be used when code needs to save and restore the exception state temporarily. UsePyErr_GetHandledException to get the exception state.

This doesn't convey the nuance here. PassingNULL to clear the exception state only works if the current exception isn't "hidden" under aNULL/None already. Also, an uninformed implementation of saving and restoring exception state usingPyErr_GetHandledException/PyErr_SetHandledException will also have issues if the top-level exception isNULL/None since it will effectively duplicate the exception. For example:

// exc_info state: None -> ExceptionPyObject*obj=PyErr_GetHandledException();// obj: Exception/* <snip some code> */PyErr_SetHandledException(obj)// exc_info state: Exception -> Exception

Maybe the documentation should just be expanded to cover this edge case, but it seems to me that it's actually a bug thatPyErr_SetHandledException doesn't set the same exception thatPyErr_GetHandledException returns.

I've just pushed a commit that fixes this issue by making sure thatPyErr_SetHandledException always sets the exception that would've been returned byPyErr_GetHandledException, which ensures that the currently-handled exception is the one that it sets. I understand that this may not be something that can be changed due to backwards compatibility concerns but thought I'd propose it anyway and see what you all think.

Open to feedback/suggestions. Also, since I think I've addressed everything at this point: I have made the requested changes; please review again.

@iritkatriel
Copy link
Member

Thanks for the analysis! Yes, I think you found the issue.

but it seems to me that it's actually a bug that PyErr_SetHandledException doesn't set the same exception that PyErr_GetHandledException returns.

I agree, and wouldn't mind seeing this fixed. Would it break anything? If you change the exception stack from[exc, None, None] to[exc, None, exc] - it look like it's semantically the same.

@pR0Ps
Copy link
ContributorAuthor

Would it break anything? If you change the exception stack from[exc, None, None] to[exc, None, exc] - it look like it's semantically the same.

I've messed around with this some more and I think you're right in that it's exactly the same from the perspective of everything just using_PyErr_GetTopmostException so it doesn't matter.

It doesn't seem to break anything (nothing in the test suite at least). The danger here is that it now makes it possible forPyErr_SetHandledException being used within a coroutine to change the exception in the calling function instead of being limited toonly being able to affect change within the context of the coroutine.

As far as I can tell, the only place the interpreter usesPyErr_SetHandledException is to set the handled exception when processing anexcept* block so the context within the block only has the matched exceptions. This only happens within the context of an exception being handled so there's no danger of the top of the stack beingNone and it overwriting the caller's exception. That being said, if some external code relies on being in a coroutine and having theexc_info stack in a state where the top exception isNone and uses this toadd an exception on top insteadsetting an exception, the change I made will cause issues. It will cause the function to replace the exception of the calling function instead of adding the exception to the top of the stack. Ex:

// <in a coroutine with the caller handling an Exception>// exc_info state: None --> ExceptionPyObject*value_err=make_some_ValueError();PyErr_SetHandledException(value_err);// PREVIOUS: ValueError --> Exception// NEW: None -> ValueError

Considering all the above, I'm reconsidering the change toPyErr_SetHandledException. Even though it seems unexpected that using it to set the handled exception toNULL/None doesn't do anything in a coroutine, I think it's much more unexpected that it can reach up out of its current context and affect the caller's exception.

While looking into this, I also realized that the change toPyErr_SetHandledException wasn't even a complete fix for theTo clear the exception state, pass NULL. documentation and the@contextmanager issue. Having it set the handled exception toNULL/None doesn't clear the exception, it removes the current topmost one. If there was another exception under there, it would be returned bysys.exception() anyway. I've added a test case in the latest commit that tests this situation.

Sorry for all the back and forth on this - I'm discovering how all this works as I go.


Assuming that the exception stack having duplicates in it is fine, the remaining issue (documentation and otherwise) is that passing inNULL/None does not actually clear the exception state - it only removes the current top. If called within a coroutine, this is either a no-op or potentially exposes the caller exception state instead.

Since it's desired behavior to havesys.exception() return the exception of the caller within a coroutine that hasn't raised an exception,_PyErr_GetTopmostException's traversing up the stack of exceptions can't be removed. In order to be able to fully clear the exception state, this means that there has to be some way of signaling to_PyErr_GetTopmostException that it shouldn't traverse up the exception stack in the specific case where the exception has been explicitly cleared.

One way to do this that looks like it might work is to use the difference betweenNULL andNone in the exception stack. If the exception isNULL (ie. unset) then it's skipped, but if it's set toNone then it's a valid non-exception and is returned as-is as the top-level exception. This allowsPyErr_SetHandledException(Py_None) to setNone on the top of the stack and mask all the exceptions under it. Then, when the coroutine exits, theNone is popped off the exception stack as normal, exposing the other exceptions under it.

I've implemented the above in my latest commit and it seems to work (all test cases pass). However, I'm definitely a bit out of my depth here so I'm not sure if this is something that can/should be changed or if I've overlooked an edge case with it.

@iritkatriel
Copy link
Member

I think it's much more unexpected that it can reach up out of its current context and affect the caller's exception.

Good point.

This is all useful analysis. Can you start by making some doc enhancements with this point you feel are missing? (If we end up making code changes we can change the docs as well, but it's useful to have the current state documented in any case).

@pR0Ps
Copy link
ContributorAuthor

Can you start by making some doc enhancements with this point you feel are missing?

Sure, should I do them in this PR or submit a new one?

@iritkatriel
Copy link
Member

Maybe a new one.

pR0Ps reacted with thumbs up emoji

@pR0Ps
Copy link
ContributorAuthor

pR0Ps commentedNov 15, 2023
edited
Loading

In parallel to that, I think I've resolved all the issues that were brought up here so@bedevere-bot : I have made the requested changes; please review again.

EDIT: ok, no idea how to get the label to change back to awaiting review so I've just manually requested a re-revew. Sorry for the spam

@pR0Ps
Copy link
ContributorAuthor

Anything else I need to do for this?

@iritkatriel
Copy link
Member

As far as I recall, the fix described in#111676 (comment) needed to be reverted so we still have a problem with the None/NULL exc_info. No?

@pR0Ps
Copy link
ContributorAuthor

I think the changes I made in the following commit of36b6177 solved that problem. Previously, aNone/NULL on the exception stack meant that_PyErr_GetTopmostException would skip it. With the new changes,_PyErr_GetTopmostException no longer skipsNones and returns them instead. This allowssys._set_exception(None) to add aNone to the exception stack, effectively clearing it.

To support this change, I modified the sections of code that add exceptions to the exception stack so that they always insertNULL in cases where there was no exception instead ofNone as they did previously.

@iritkatriel
Copy link
Member

I think the changes I made in the following commit of36b6177 solved that problem. Previously, aNone/NULL on the exception stack meant that_PyErr_GetTopmostException would skip it. With the new changes,_PyErr_GetTopmostException no longer skipsNones and returns them instead. This allowssys._set_exception(None) to add aNone to the exception stack, effectively clearing it.

To support this change, I modified the sections of code that add exceptions to the exception stack so that they always insertNULL in cases where there was no exception instead ofNone as they did previously.

This makes sense, but since it's a deep subtle change I think we should split it into two PRs. That would make it easier in the future to see what happened. Both PRs can be attached to this issue.

The first PR would get rid of theNULL/None ambiguity. Add an assertion in_PyErr_GetTopmostException that the exception is neverNone. The second PR can contain the rest.

pR0Ps reacted with thumbs up emoji

pR0Ps added a commit to pR0Ps/cpython that referenced this pull requestDec 20, 2023
Previously, both `NULL` and `Py_None` would be used interchangeably toindicate that an exception is no longer being handled. By ensuring thatonly `NULL` is used, this opens up the possibility to use `Py_None` toindicate a cleared exception. The difference here would be that clearingwould indicate that no exception is currently being handled vs. handlingwould indicate that the next exception in the stack is currently beinghandled.This functionality will be used to patch up some edge cases in how theexception context interacts with exceptions thrown into coroutines.This is implemented in this commit by changing code that could add`Py_None` to the exception stack to indicate that an exception is nolonger being handled to add `NULL` instead. An assert was also added toensure that `Py_None` is no longer added to the exception stack.Seepythongh-111676 for context.
pR0Ps added a commit to pR0Ps/cpython that referenced this pull requestDec 20, 2023
Previously, both `NULL` and `Py_None` would be used interchangeably toindicate that an exception is no longer being handled. By ensuring thatonly `NULL` is used, this opens up the possibility to use `Py_None` toindicate a cleared exception. The difference here would be that clearingwould indicate that no exception is currently being handled vs. handlingwould indicate that the next exception in the stack is currently beinghandled.This functionality will be used to patch up some edge cases in how theexception context interacts with exceptions thrown into coroutines.This is implemented in this commit by changing code that could add`Py_None` to the exception stack to indicate that an exception is nolonger being handled to add `NULL` instead. An assert was also added toensure that `Py_None` is no longer added to the exception stack.Seepythongh-111676 for context.
pR0Ps added a commit to pR0Ps/cpython that referenced this pull requestDec 20, 2023
Previously, both `NULL` and `Py_None` would be used interchangeably toindicate that an exception is no longer being handled. By ensuring thatonly `NULL` is used, this opens up the possibility to use `Py_None` toindicate a cleared exception. The difference here would be that clearingwould indicate that no exception is currently being handled vs. handlingwould indicate that the next exception in the stack is currently beinghandled.This functionality will be used to patch up some edge cases in how theexception context interacts with exceptions thrown into coroutines.This is implemented in this commit by changing code that could add`Py_None` to the exception stack to indicate that an exception is nolonger being handled to add `NULL` instead. An assert was also added toensure that `Py_None` is no longer added to the exception stack.Seepythongh-111676 for context.
@pR0PspR0Psforce-pushed thebugfix/with-statement-exception-handling branch fromedd0f5b toa6f948bCompareDecember 20, 2023 04:14
@pR0Ps
Copy link
ContributorAuthor

pR0Ps commentedDec 20, 2023
edited
Loading

The first PR would get rid of theNULL/None ambiguity. Add an assertion in_PyErr_GetTopmostException that the exception is neverNone. The second PR can contain the rest.

I've created#113302 that fixes theNULL/None ambiguity and adds theassert and rebased this PR on top of that branch. I also took the opportunity while I was rewriting history to fix the merge conflicts withmain and rewrite the commits here into something that's hopefully more understandable to reviewers. It now only includes 2 commits: one to addsys._set_exception() and thePy_None exception masking, and one to use that functionality to fix the@contextmanager behavior.

Apologies for this, I know rebasing PRs during development is generally frowned upon, but figured the alternative of having the same functionality implemented in different ways across multiple branches, having merge conflicts withmain, and not being able to have it cleanly apply on#113302 was worse in this case.

@@ -0,0 +1,2 @@
Add sys._set_exception() function that can set/clear the current exception
context. Patch by Carey Metcalfe.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

We have 3 news files here. This and the next one should be deleted.

Copy link
ContributorAuthor

@pR0PspR0PsDec 22, 2023
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Will do. Should thesys._set_exception() be added to the other news file or just not noted at all since it's an "internal" function? (providedsys._set_exception() is how this ends up being implemented, otherwise this is a moot point)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Probably not needed if it's private. But you can wait with the news file till we know what's going in.

pR0Ps reacted with thumbs up emoji
# context here. In order to make the context manager behave
# like a normal function we set the current exception context
# to what it was during the context manager's __enter__
sys._set_exception(exc_context)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

TBH, I'm not that comfortable adding this function to thesys module, even with an_. It is an API that shouldn't really exist, we're adding it just to enable a hack in contextmanager, which needs to change the semantics of code.

Is there a way that we can roll this functionality into thegen.throw() function instead? Such as -- add a parameter that indicates that the exception stack needs to be fudged in this way?

Copy link
ContributorAuthor

@pR0PspR0PsDec 22, 2023
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Yeah, that makes sense. I don't really have strong opinions on how it's implemented, I only went withsys._set_exception because it seemed like it would work andthere is precedent for a function that directly manipulates the current exception state.

With that being said, I don't know if doing it entirely withinthrow() is possible, at least in this case. Because the exception that needs to be set is the one being handled in the__enter__, I don't think there's enough context whenthrow() is called to correctly manipulate the current exception.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I was thinking you pass the exc_context as a new kwwarg to throw which defaults to None.

(I wasn't aware of exc_clear actually. It was added in 2.3, so quite old. Doesn't seem to be used anywhere in the stdlib at the moment.)

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Ah, that makes sense. Now that I've gotsys._set_exception() working and am therefore (reasonably) sure that theNone/NULL exception stuff works properly, I'll take a look at adding a kwarg tothrow() that essentially just makes it callPyErr_SetHandledException behind the scenes.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I've added some commits that hides the manipulation of the exception context within the.throw(..) call of generator/coroutines.

The first commit had issues wherethrowing an exception into a generator that cleared it would clear the exception in the current handler. The second commits fixes this up by saving and restoring the current exception state.

The way I implemented it was that I added anexc_context=None kwarg togen_throw inObjects/genobject.c that passes the exception into_gen_throw._gen_throw then saves the currentexc_value and sets it toexc_context (orPy_None to hide exceptions under it in the stack). It then callsPyErr_Restore to set the thrown exception as the top-level exception as normal, runs the generator, then restores the savedexc_value so thethrow call doesn't update the current exception.

Note that I'm not familiar with the conventions around_PyArg_UnpackKeywords so I've probably not done that part properly.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Generator tests are failing, will take a look at fixing them.

Some potential issues with addingexc_context tothrow now that I've had a first pass at it:

  • It seems like the implementation of doing the exception context handling within.throw() is likely to have edge cases since it's a fairly complex system that a lot of things use.
  • Having to change thethrow function fromMETH_FASTCALL toMETH_FASTCALL | METH_KEYWORDS is going to imposesome performance penalty, but I don't know how significant of an issue this will be.
  • Adding a newexc_context kwarg to.throw() specifically for contextlib's weird edge case seems like not enough benefit to be worth it (unless you can think of any other use cases for it - I haven't been able to).

Would it be acceptable to hide the current_set_exception function under contextlib somewhere (contextlib._setup_genmgr_ctx?) so it's not as inviting-looking and to make it clear that it's a contextlib-specific hack? The issue of callingPyErr_SetHandledException to modify the current exception context is largely not an issue in the one specific contextlib case, but I agree that it's problematic generally.

@iritkatriel what do you think?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I've fixed the failing tests by defaultingthrow()'sexc_context parameter tosys.exception() if it's not provided and tweaking the logic in_gen_throw a bit.

Theexc_context implementation can definitely be improved, but I think this is good enough as a proof of concept to decide if this is the right way to solve this issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Would it be acceptable to hide the current_set_exception function under contextlib somewhere (contextlib._setup_genmgr_ctx?) so it's not as inviting-looking and to make it clear that it's a contextlib-specific hack? The issue of callingPyErr_SetHandledException to modify the current exception context is largely not an issue in the one specific contextlib case, but I agree that it's problematic generally.

@iritkatriel what do you think?

Sounds reasonable.

I still need to get back into this issue fully. Will try to allocate time next week.

@pR0PspR0Psforce-pushed thebugfix/with-statement-exception-handling branch 2 times, most recently from5e7f10b to887eb54CompareDecember 24, 2023 21:04
pR0Psand others added7 commitsMay 19, 2025 14:15
…nctionExposing this function allows Python code to clear/set the currentexception context as returned by `sys.exception()`. It is prefixed by anunderscore since this functionality is not intended to be accessed byuser-written code.In order to allow `sys._set_exception(None)` to clear the currentexception `_PyErr_GetTopmostException` was changed to consider `Py_None`a valid exception instead of an indication to keep traversing the stacklike `NULL` is. Additionally, `PyErr_SetHandledException` was updated toadd `Py_None` to the exception stack instead of `NULL`.Put together, these changes allow `PyErr_SetHandledException(NULL)` tomask the entire exception chain and "clear" the current exception stateas documented in `Doc/c-api/exceptions.rst`. Furthermore, since`sys._set_exception()` uses `PyErr_SetHandledException`, this allows`sys._set_exception(None)` to clear the current exception contextinstead of just exposing the next exception in the stack.
Previously, when using a `try...yield...except` construct within a`@contextmanager` function, an exception raised by the `yield` wouldn'tbe properly cleared when handled the `except` block. Instead, calling`sys.exception()` would continue to return the exception, leading toconfusing tracebacks and logs.This was happening due to the way that the `@contextmanager` decoratordrives its decorated generator function. When an exception occurs, ituses `.throw(exc)` to throw the exception into the generator. However,even if the generator handles this exception, because the exception wasthrown into it in the context of the `@contextmanager` decoratorhandling it (and not being finished yet), `sys.exception()` was notbeing reset.In order to fix this, the exception context as the `@contextmanager`decorator is `__enter__`'d is stored and set as the current exceptioncontext just before throwing the new exception into the generator.Doing this means that after the generator handles the thrown exception,`sys.exception()` reverts back to what it was when the `@contextmanager`function was started.
Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com>
@pR0PspR0Psforce-pushed thebugfix/with-statement-exception-handling branch from887eb54 toc0e7400CompareMay 19, 2025 19:06
This avoids accidentally overriding the exception state of the currentthread when calling `.throw()` (what happened in the previous commit)
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@iritkatrieliritkatrieliritkatriel left review comments

@ericsnowcurrentlyericsnowcurrentlyericsnowcurrently requested changes

@1st11st1Awaiting requested review from 1st11st1 is a code owner

@markshannonmarkshannonAwaiting requested review from markshannonmarkshannon is a code owner

@gvanrossumgvanrossumAwaiting requested review from gvanrossum

Assignees
No one assigned
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

3 participants
@pR0Ps@iritkatriel@ericsnowcurrently

[8]ページ先頭

©2009-2025 Movatter.jp