Skip to content
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

Merged
merged 1 commit into from
Aug 16, 2020

Conversation

jbarlow83
Copy link
Contributor

To deal with exceptions that hit destructors or other noexcept functions.

Supercedes #2358

@virtuald @YannickJadoul

Copy link
Contributor

@virtuald virtuald left a comment

Choose a reason for hiding this comment

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

LGTM

@jbarlow83
Copy link
Contributor Author

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.

Copy link
Collaborator

@YannickJadoul YannickJadoul left a 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)?

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,
Copy link
Collaborator

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?

Copy link
Contributor Author

@jbarlow83 jbarlow83 Aug 10, 2020

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.

Copy link
Collaborator

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 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.

Alright, yes, I can see. In that case, this is definitely better than what we had, so more changes can come later! :-)

docs/advanced/classes.rst Show resolved Hide resolved
docs/advanced/classes.rst Show resolved Hide resolved
docs/advanced/exceptions.rst Outdated Show resolved Hide resolved
docs/advanced/exceptions.rst Outdated Show resolved Hide resolved
@jbarlow83 jbarlow83 force-pushed the pr branch 4 times, most recently from ac4a891 to 9ca68ca Compare August 11, 2020 09:00
Copy link
Collaborator

@YannickJadoul YannickJadoul left a 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?

@jbarlow83 jbarlow83 force-pushed the pr branch 3 times, most recently from 05ca399 to 5437330 Compare August 11, 2020 18:54
@jbarlow83
Copy link
Contributor Author

@YannickJadoul CI is fixed

@YannickJadoul
Copy link
Collaborator

@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?

@jbarlow83
Copy link
Contributor Author

Yes, should be ready

Copy link
Collaborator

@henryiii henryiii left a 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.

@jbarlow83
Copy link
Contributor Author

@henryiii #2383 is different, added a comment there.

Copy link
Collaborator

@rwgk rwgk left a 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.

PyErr_WriteUnraisable(err_context.ptr());
}
void discard_as_unraisable(const char *err_context) {
auto obj = reinterpret_steal<object>(PYBIND11_FROM_STRING(err_context));
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Copy link
Collaborator

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 :-/

assert triggered[0] is True

captured = capsys.readouterr()
captured_err = captured[1] # Python 3.5 doesn't like .err the namedtuple
Copy link
Collaborator

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
?

Copy link
Contributor Author

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.

if hooked:
assert triggered[0] is True

captured_err = capsys.readouterr()[1] # Returns (stdout, syserr)
Copy link
Collaborator

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.

Copy link
Collaborator

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.
@rwgk
Copy link
Collaborator

rwgk commented Aug 16, 2020

Hi @jbarlow83, thanks for the contribution! I'll merge this now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants