-
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
Add and document py::error_already_set::discard_as_unraisable() #2372
Conversation
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.
LGTM
Added a missing documentation file I edited and forgot to include, and fixed Python 2.7 support. I believe the current CI errors are due to recent changes in the CMake setup. |
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.
Nice addition :-) Thanks for taking the time to work on this, @jbarlow83!
I personally don't need this in my project, I believe, so I don't have problems with keeping this a very thin/minor PR (mostly in the docs), but I'm wondering why we're not including the other ideas (e.g., the lambda-wrapping utility function)?
docs/advanced/classes.rst
Outdated
of type :class:`error_already_set`. If this error is thrown out of a class destructor, | ||
``std::terminate()`` will be called, terminating the process. Class destructors | ||
must catch all exceptions of type :class:`error_already_set` to discard the Python | ||
exception using :meth:`error_already_set::discard_as_unraisable`. (In addition, |
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.
Minor thing: I don't think the parentheses are needed here. This is important enough to realize :-)
Another minor thing: Maybe mention things like py::cast_error
, etc, that are also C++ errors and not caught by py::error_already_set
?
Major thing: reading this, I wonder "what do you do then, for C++ excecptions? Maybe we should still provide a wrapper function such that users can do this without calling the C API?
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.
Do you mean std::terminate()
-> std::terminate
?
I think classes.rst needs a bigger overhaul than this but was trying to limit the scope of changes for now. In particular, py::error_already_set
serves a different function from the C++ exception wrappers, and needs to be separately explained separately from them and then an example added that brings the two concepts together. In particular, I find it pretty unintuitive that if you have a Python -> C++ -> Python callback situation, and you throw ValueError from Python, you can't catch py::value_error
. You can only catch py::value_error
when another C++ function throws it.
I think we could add py::builtin_exception::discard_as_unraisable
. It's a little trickier to convert a std::exception
into a Python exception, because I don't think we should invoke the exception translator as part of dealing with an unraisable exception.
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.
Do you mean
std::terminate()
->std::terminate
?
No, sorry, I meant the parentheses around "In addition, ...".
I think classes.rst needs a bigger overhaul than this but was trying to limit the scope of changes for now. In particular,
py::error_already_set
serves a different function from the C++ exception wrappers, and needs to be separately explained separately from them and then an example added that brings the two concepts together.We could add
py::builtin_exception::discard_as_unraisable
. It's a little trickier to convert astd::exception
into a Python exception, because I don't think we should invoke the exception translator as part of dealing with an unraisable exception.
Alright, yes, I can see. In that case, this is definitely better than what we had, so more changes can come later! :-)
ac4a891
to
9ca68ca
Compare
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.
Right. If this is ready, I'll happily press "merge"!
EDIT: Oh, wait. First CI needs to be fixed. But I'm guessing that's linked to #2381?
05ca399
to
5437330
Compare
@YannickJadoul CI is fixed |
@jbarlow83 Great! So, ready? Let's maybe still get a quick review from a few others. @bstaletic, @henryiii, @rwgk, and/or @EricCousineau-TRI, anything to add/change? |
Yes, should be ready |
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.
Just curious, could this be used to help with #2383? The problem there is similar, pybind11 tries to call __repr__
and if that throws, it crashes with an error_already_set
.
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.
Thanks, looks great to me. Just a couple minor suggestions.
include/pybind11/pytypes.h
Outdated
PyErr_WriteUnraisable(err_context.ptr()); | ||
} | ||
void discard_as_unraisable(const char *err_context) { | ||
auto obj = reinterpret_steal<object>(PYBIND11_FROM_STRING(err_context)); |
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.
discard_as_unraisable(reinterpret_steal<object>(PYBIND11_FROM_STRING(err_context)));
?
to avoid code duplication.
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.
Sounds good. BTW reason for not calling str
here is that it is defined before str
is.
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.
Hmmm, guess there's no way to forward declare that?
I suppose it's not worth the effort to try rearranging things :-/
tests/test_exceptions.py
Outdated
assert triggered[0] is True | ||
|
||
captured = capsys.readouterr() | ||
captured_err = captured[1] # Python 3.5 doesn't like .err the namedtuple |
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.
# captured.err does not work with Python 3.5
?
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.
The captured
object is a namedtuple
or similar object with a namedtuple-like interface.
We'd want to say captured.err
here, but that failed in CI on Python 3.5 on Debian and nowhere else. Perhaps it's specific to a particular minor revision. I'll fix the typo.
tests/test_exceptions.py
Outdated
if hooked: | ||
assert triggered[0] is True | ||
|
||
captured_err = capsys.readouterr()[1] # Returns (stdout, syserr) |
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.
typo? Should this be stderr
?
If you fix that I'll merge this PR, we have 4 approvals already.
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.
FWIW If capsys.readouterr()
always returns that tuple, then consider doing tuple unpacking:
_, captures_err = capsys.readouterr()
To deal with exceptions that hit destructors or other noexcept functions. Includes fixes to support Python 2.7 and extends documentation on error handling. @virtuald and @YannickJadoul both contributed to this PR.
Hi @jbarlow83, thanks for the contribution! I'll merge this now. |
To deal with exceptions that hit destructors or other noexcept functions.
Supercedes #2358
@virtuald @YannickJadoul