-
-
Notifications
You must be signed in to change notification settings - Fork 30.8k
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
Suspected PyErr_Fetch()
behavior change
#102594
Comments
Thanks @corona10 . A cc in a comment is enough to bring an issue to someone's attention (assigned means something different and could prevent others from taking a look). |
Ooop sorry. I will care! |
@rwgk If not with the python unit test as described above, a c program reproducing the issue would also be helpful. |
I created two PRs:
GHA for #102606 never finished, but local testing passes. GHA result for the #102607:
FWIW, I also tested locally with efc985a: Behavior also/already changed. |
Thank you for the full repro. When I tried to just call The change in behaviour is in SetObject - the exception is now being normalised there, and stored in the interpreter in its normalised form, instead of being stored in the denormalised form and needing to be normalised after Fetch. In the case of your exception, which raises a value error from its I think we should also deprecate PyErr_SetObject, and direct users to use PyErr_SetRaisedException() instead (so they will have to create the exception instance beforehand, and there will be no room for confusion about which exception is being set as raised). As for the change in behaviour reported here - I don't see a way to make this snippet behave as before, without reverting #101607. And I don't think we want to revert that for this edge case. I think we should document the change and deprecate PyErr_SetObject. There is a precedent for something similar in 3.11 - see the edge cases mentioned as changed due to bpo-45711 in the release notes. CC @markshannon |
The docs are vague. I think the old behavior was broken and that it is now fixed.
>>> class BorkedException(Exception):
... def __init__(self):
... raise ValueError("wot?")
...
>>> raise BorkedException
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
ValueError: wot? Issue about the strange behavior of +1 to deprecating |
The new behavior masks errors in the error handling, which is ... terrible, in lack of a more diplomatic word. A behavior change seems desirable, but if the behavior is changed, I think it's important to make a decision based on a careful and thorough assessment of the pros and cons. When I was working on pybind/pybind11#1895 last year, I was struck by the apparent blissful ignorance in the PyErr_* APIs. Implicitly the APIs assume that the error handling code is bug free. That's of course not a reality, in particular in large-scale systems (I'm at Google). I know from first-hand experience that masked errors in the error handling can take weeks to troubleshoot, especially in a complex system with hundreds of thousands of dependencies (no exaggeration). (Actually, we never got to the bottom of one particular problem that was triggered by a change I made deep down in the core of our stack. I wound up spending a couple months revamping some core components around PyCLIF, and then sinking a couple weeks just into pybind/pybind11#1895, in preparation for making pybind11 a critical part of the Google infrastructure.) This code is the result of extensive experiments and discussions (under pybind/pybind11#1895) to find a way to NOT mask errors in the error handling: I believe
That's not good, too, for the same reasons. It masks errors and can potentially add weeks of delays to projects like upgrading from Python 3.N to 3.N+1, or modernizing core components more generally. |
The new APIs don’t need to deal with mismatch between normalised and non-normalised exceptions because they only have normalised exceptions now. We are in the process of deprecating the non-normalised form. |
I don't think it does. If instantiation of the What did you expect to happen in this case?
We did. There really don't seem to be any cons. The earlier normalization seems to surface latent bugs (such as the one in your If you are handling flaky exception classes, why not create the exception first, then set the exception? Here's the code to create, then set the exception: PyObject *ex = PyObject_CallOneArg(exc_type, value);
if (ex == NULL) {
/* handle the error */
}
else {
PyErr_SetRaisedException(ex);
} |
I don't consider process termination when exception normalization fails acceptable, given that the example A possible compromise could be to raise a third exception, whose constructor by design cannot fail (except when running out of memory, in which case we can just let the class ExceptionNormalizationFailed(BaseException):
def __init__(self, intended: type[BaseException], arg: object, actual: BaseException):
super().__init__(intended, arg, actual)
self.intended = intended
self.arg = arg
self.actual = actual
# maybe also set __cause__ or __context__ to actual? And/or set __traceback__ from actual.__traceback__?
def __str__(self):
return f"Attempt to construct {self.intended!r} instance with arg[s] {self.arg!r} raised {self.actual!r}" (This is just a strawman design, all the details are up for discussion.) This would be raised whenever normalization raises an exception that isn't an instance of the intended exception. In the
Of course this would still be a change in behavior compared to Python 3.11 and before, so I don't know if it's acceptable to the OP. |
I really like this suggestion and would be more than happy to tweak pybind11 accordingly. |
The OP:
|
I like |
I think in the context of pybind11 that's not really an option, because that would push the responsibility to the user code. The pattern is:
When |
If you're already tweaking pybind11, wouldn't you rather follow @markshannon 's suggestion and have full control over how you handle errors? I see the advantage of ExceptionNormalizationFailed in that it can provide full information about what happened in programs that call PyErr_SetObject as is. But really we should deprecate construct+set in one function. |
How about if instead of ExceptionNormalizationFailed, we raise the exception that came from |
A note sounds great because it is much simpler than the proposed new exception. |
Sounds good to me, too. (I didn't know about notes until just now TBH.) |
What if |
I’ll check what the traceback printing code does with such errors and do the same thing. |
Sounds good. The approach I took for something similar/related in pybind11 is to generate messages like I made an attempt to generate messages that can relatively easily be pin-pointed in potentially gigantic log files. |
* main: (34 commits) pythongh-102701: Fix overflow in dictobject.c (pythonGH-102750) pythonGH-78530: add support for generators in `asyncio.wait` (python#102761) Increase stack reserve size for Windows debug builds to avoid test crashes (pythonGH-102764) pythongh-102755: Add PyErr_DisplayException(exc) (python#102756) Fix outdated note about 'int' rounding or truncating (python#102736) pythongh-102192: Replace PyErr_Fetch/Restore etc by more efficient alternatives (python#102760) pythongh-99726: Improves correctness of stat results for Windows, and uses faster API when available (pythonGH-102149) pythongh-102192: remove redundant exception fields from ssl module socket (python#102466) pythongh-102192: Replace PyErr_Fetch/Restore etc by more efficient alternatives (python#102743) pythongh-102737: Un-ignore ceval.c in the CI globals check (pythongh-102745) pythonGH-102748: remove legacy support for generator based coroutines from `asyncio.iscoroutine` (python#102749) pythongh-102721: Improve coverage of `_collections_abc._CallableGenericAlias` (python#102722) pythonGH-102653: Make recipe docstring show the correct distribution (python#102742) Add comments to `{typing,_collections_abc}._type_repr` about each other (python#102752) pythongh-102594: PyErr_SetObject adds note to exception raised on normalization error (python#102675) pythongh-94440: Fix issue of ProcessPoolExecutor shutdown hanging (python#94468) pythonGH-100112: avoid using iterable coroutines in asyncio internally (python#100128) pythongh-102690: Use Edge as fallback in webbrowser instead of IE (python#102691) pythongh-102660: Fix Refleaks in import.c (python#102744) pythongh-102738: remove from cases generator the code related to register instructions (python#102739) ...
Bug report
While testing pybind11 with Python 3.12alpha6 I ran into what looks like a
PyErr_Fetch()
behavior change. I also tried with the main branch @ c6858d1.I think the issue reduces to:
PyErr_SetObject()
as e.g. here: https://github.com/pybind/pybind11/blob/442261da585536521ff459b1457b2904895f23b4/tests/test_exceptions.cpp#L309Followed more-or-less immediately by a
PyErr_Fetch()
as e.g. here: https://github.com/pybind/pybind11/blob/442261da585536521ff459b1457b2904895f23b4/include/pybind11/pytypes.h#L482The special twist is that the exception type involved raises a
ValueError
in its__init__
:For easy reference the same code copy-pasted here:
Up to and including 3.12alpha3:
PyErr_Fetch()
produces theFlakyException
type (this here).With 3.12alpha6:
PyErr_Fetch()
produces theValueError
type instead.Additional detail:
Up to and including 3.12alpha3: only
PyErr_NormalizeException()
hits theValueError
inFlakyException._init__
, but notPyErr_Fetch()
.Your environment
Linked PRs
The text was updated successfully, but these errors were encountered: