-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Fix exception handling when pybind11::weakref() fails. #3739
Conversation
The weakref() constructor calls pybind11_fail() without clearing any Python interpreter error state. If a client catches the C++ exception thrown by pybind11_fail(), the Python interpreter will be left in an error state.
What is the PyErr flag set to? I see two possible alternative solutions:
I gave a stab of 1. on master in Debug mode, but it didn't trigger any assert failures which tells me that this error isn't caught by our current test suite. @hawkinsp can you verify this is the case and add an appropriate unit test. @henryiii @rwgk I'd appreciate hearing your input on what the best way to harden pybind11_fail to this would be. |
@Skylion007 I may not have time to add a test to the PR, but the idea is like this:
You can reproduce by simply calling |
@Skylion007 wrote:
I really like that idea. Yesterday on chat @hawkinsp had the idea to move the change into the I haven't looked, could use |
I really feel like the proper thing to do here is just remove our pybind11_fail and propogate the typeerror with error_already_set(). Thoughts @henryiii @rwgk ? It's a very minor breaking change, but one that will match PyBInd11 functionality with Python a bit better. I actually don't like raise_from in this context UNLESS we really have to preserve backwards compatability as the errors will be of different types if it's from PyBind11 vs Python (Runtime vs TypeError). |
I'm all for closer matching of pybind11 with Python, personally. We can mention it in the upgrade guide. |
We're going from 2.9 to 2.10 anyway, that would seem fine. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it best to test that PyPy doesn't show this behavior (as done now), or skip/xfail the test on PyPy? Guessing this behavior is likely to remain unchanged on PyPy, which would be the assumption in the current implementation, I think that's fine.
@henryiii CPython says weakref is an implementation detail. If PyPy behavior changes when we update PyPy than we can make it an xfail. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thanks a lot @Skylion007 for hammering it into such great shape!
# Should raise TypeError on CPython | ||
with pytest.raises(TypeError) if not env.PYPY else contextlib.nullcontext(): | ||
if has_callback: | ||
_ = create_weakref(ob, callback) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the _
important here? (Guessing yes, but asking JIC it's a leftover from some experiment.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just wanted to annotate that there was a return value we were throwing away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense! Thanks for the explanation.
I'll run the global testing for this PR as soon as I can. Unfortunately the previous attempt is still stuck in the queue (bad infrastructure day). |
Globally tested with the 2022-02-18 00:00 batch. All good! |
Whoops, @henryiii forgot to fix the initial PR to include a changelog. What's the best way to retrofit? |
? We don't ever include changelogs in the PR. Just make sure it's in the description so that |
(done) |
The
weakref()
constructor callspybind11_fail()
without clearing anyPython interpreter error state. If a client catches the C++ exception
thrown by
pybind11_fail()
, the Python interpreter will be left in anerror state.
I found this problem while fixing code that called
pybind11::weakref(obj)
whereobj
is not weak-referenceable. In that case, I wanted to fallback to another code path by catching the exception raised bypybind11_fail
, e.g.,However, if you do this, then the Python error flag is left set in the catch case and not cleared. This causes later calls into the Python interpreter to fail.
It is possible that this PR should be more aggressive: all calls to
pybind11_fail()
should clear the error flag. If we are going to throw a C++ exception, then presumably that is replacing any Python error state.Suggested changelog entry:
* Fix exception handling when ``pybind11::weakref()`` fails