2016-06-17 21:35:59 +00:00
|
|
|
/*
|
2016-08-12 11:50:00 +00:00
|
|
|
tests/test_custom-exceptions.cpp -- exception translation
|
2016-06-17 21:35:59 +00:00
|
|
|
|
|
|
|
Copyright (c) 2016 Pim Schellart <P.Schellart@princeton.edu>
|
|
|
|
|
|
|
|
All rights reserved. Use of this source code is governed by a
|
|
|
|
BSD-style license that can be found in the LICENSE file.
|
|
|
|
*/
|
2021-05-27 15:00:18 +00:00
|
|
|
#include "test_exceptions.h"
|
2021-06-22 16:11:54 +00:00
|
|
|
|
2021-07-21 12:22:18 +00:00
|
|
|
#include "local_bindings.h"
|
2016-08-12 11:50:00 +00:00
|
|
|
#include "pybind11_tests.h"
|
2022-02-10 20:17:07 +00:00
|
|
|
|
2022-01-14 19:22:47 +00:00
|
|
|
#include <exception>
|
|
|
|
#include <stdexcept>
|
2021-06-22 16:11:54 +00:00
|
|
|
#include <utility>
|
2016-06-17 21:35:59 +00:00
|
|
|
|
2018-01-09 17:30:19 +00:00
|
|
|
// A type that should be raised as an exception in Python
|
2016-06-17 21:35:59 +00:00
|
|
|
class MyException : public std::exception {
|
|
|
|
public:
|
2022-02-10 20:17:07 +00:00
|
|
|
explicit MyException(const char *m) : message{m} {}
|
|
|
|
const char *what() const noexcept override { return message.c_str(); }
|
|
|
|
|
2016-06-17 21:35:59 +00:00
|
|
|
private:
|
|
|
|
std::string message = "";
|
|
|
|
};
|
|
|
|
|
|
|
|
// A type that should be translated to a standard Python exception
|
|
|
|
class MyException2 : public std::exception {
|
|
|
|
public:
|
2022-02-10 20:17:07 +00:00
|
|
|
explicit MyException2(const char *m) : message{m} {}
|
|
|
|
const char *what() const noexcept override { return message.c_str(); }
|
|
|
|
|
2016-06-17 21:35:59 +00:00
|
|
|
private:
|
|
|
|
std::string message = "";
|
|
|
|
};
|
|
|
|
|
|
|
|
// A type that is not derived from std::exception (and is thus unknown)
|
|
|
|
class MyException3 {
|
|
|
|
public:
|
2022-02-10 20:17:07 +00:00
|
|
|
explicit MyException3(const char *m) : message{m} {}
|
|
|
|
virtual const char *what() const noexcept { return message.c_str(); }
|
2020-10-29 13:12:14 +00:00
|
|
|
// Rule of 5 BEGIN: to preempt compiler warnings.
|
2022-02-10 20:17:07 +00:00
|
|
|
MyException3(const MyException3 &) = default;
|
|
|
|
MyException3(MyException3 &&) = default;
|
|
|
|
MyException3 &operator=(const MyException3 &) = default;
|
|
|
|
MyException3 &operator=(MyException3 &&) = default;
|
2020-10-29 13:12:14 +00:00
|
|
|
virtual ~MyException3() = default;
|
|
|
|
// Rule of 5 END.
|
2016-06-17 21:35:59 +00:00
|
|
|
private:
|
|
|
|
std::string message = "";
|
|
|
|
};
|
|
|
|
|
|
|
|
// A type that should be translated to MyException
|
|
|
|
// and delegated to its exception translator
|
|
|
|
class MyException4 : public std::exception {
|
|
|
|
public:
|
2022-02-10 20:17:07 +00:00
|
|
|
explicit MyException4(const char *m) : message{m} {}
|
|
|
|
const char *what() const noexcept override { return message.c_str(); }
|
|
|
|
|
2016-06-17 21:35:59 +00:00
|
|
|
private:
|
|
|
|
std::string message = "";
|
|
|
|
};
|
|
|
|
|
2016-09-16 06:04:15 +00:00
|
|
|
// Like the above, but declared via the helper function
|
|
|
|
class MyException5 : public std::logic_error {
|
|
|
|
public:
|
|
|
|
explicit MyException5(const std::string &what) : std::logic_error(what) {}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Inherits from MyException5
|
|
|
|
class MyException5_1 : public MyException5 {
|
|
|
|
using MyException5::MyException5;
|
|
|
|
};
|
|
|
|
|
2021-07-21 12:22:18 +00:00
|
|
|
// Exception that will be caught via the module local translator.
|
|
|
|
class MyException6 : public std::exception {
|
|
|
|
public:
|
2022-02-10 20:17:07 +00:00
|
|
|
explicit MyException6(const char *m) : message{m} {}
|
|
|
|
const char *what() const noexcept override { return message.c_str(); }
|
|
|
|
|
2021-07-21 12:22:18 +00:00
|
|
|
private:
|
|
|
|
std::string message = "";
|
|
|
|
};
|
|
|
|
|
2016-09-10 09:58:02 +00:00
|
|
|
struct PythonCallInDestructor {
|
CodeHealth: Enabling clang-tidy google-explicit-constructor (#3250)
* Adding google-explicit-constructor to .clang-tidy
* clang-tidy explicit attr.h (all automatic)
* clang-tidy explicit cast.h (all automatic)
* clang-tidy detail/init.h (1 NOLINT)
* clang-tidy detail/type_caster_base.h (2 NOLINT)
* clang-tidy pybind11.h (7 NOLINT)
* clang-tidy detail/common.h (3 NOLINT)
* clang-tidy detail/descr.h (2 NOLINT)
* clang-tidy pytypes.h (23 NOLINT, only 1 explicit)
* clang-tidy eigen.h (7 NOLINT, 0 explicit)
* Adding 2 explicit in functional.h
* Adding 4 explicit in iostream.h
* clang-tidy numpy.h (1 NOLINT, 1 explicit)
* clang-tidy embed.h (0 NOLINT, 1 explicit)
* clang-tidy tests/local_bindings.h (0 NOLINT, 4 explicit)
* clang-tidy tests/pybind11_cross_module_tests.cpp (0 NOLINT, 1 explicit)
* clang-tidy tests/pybind11_tests.h (0 NOLINT, 2 explicit)
* clang-tidy tests/test_buffers.cpp (0 NOLINT, 2 explicit)
* clang-tidy tests/test_builtin_casters.cpp (0 NOLINT, 4 explicit)
* clang-tidy tests/test_class.cpp (0 NOLINT, 6 explicit)
* clang-tidy tests/test_copy_move.cpp (0 NOLINT, 7 explicit)
* clang-tidy tests/test_embed/external_module.cpp (0 NOLINT, 1 explicit)
* clang-tidy tests/test_embed/test_interpreter.cpp (0 NOLINT, 1 explicit)
* clang-tidy tests/object.h (0 NOLINT, 2 explicit)
* clang-tidy batch of fully automatic fixes.
* Workaround for MSVC 19.16.27045.0 C++17 Python 2 C++ syntax error.
2021-09-09 01:53:38 +00:00
|
|
|
explicit PythonCallInDestructor(const py::dict &d) : d(d) {}
|
2016-12-12 22:42:52 +00:00
|
|
|
~PythonCallInDestructor() { d["good"] = true; }
|
2016-09-10 09:58:02 +00:00
|
|
|
|
|
|
|
py::dict d;
|
|
|
|
};
|
|
|
|
|
2020-08-08 10:07:14 +00:00
|
|
|
struct PythonAlreadySetInDestructor {
|
CodeHealth: Enabling clang-tidy google-explicit-constructor (#3250)
* Adding google-explicit-constructor to .clang-tidy
* clang-tidy explicit attr.h (all automatic)
* clang-tidy explicit cast.h (all automatic)
* clang-tidy detail/init.h (1 NOLINT)
* clang-tidy detail/type_caster_base.h (2 NOLINT)
* clang-tidy pybind11.h (7 NOLINT)
* clang-tidy detail/common.h (3 NOLINT)
* clang-tidy detail/descr.h (2 NOLINT)
* clang-tidy pytypes.h (23 NOLINT, only 1 explicit)
* clang-tidy eigen.h (7 NOLINT, 0 explicit)
* Adding 2 explicit in functional.h
* Adding 4 explicit in iostream.h
* clang-tidy numpy.h (1 NOLINT, 1 explicit)
* clang-tidy embed.h (0 NOLINT, 1 explicit)
* clang-tidy tests/local_bindings.h (0 NOLINT, 4 explicit)
* clang-tidy tests/pybind11_cross_module_tests.cpp (0 NOLINT, 1 explicit)
* clang-tidy tests/pybind11_tests.h (0 NOLINT, 2 explicit)
* clang-tidy tests/test_buffers.cpp (0 NOLINT, 2 explicit)
* clang-tidy tests/test_builtin_casters.cpp (0 NOLINT, 4 explicit)
* clang-tidy tests/test_class.cpp (0 NOLINT, 6 explicit)
* clang-tidy tests/test_copy_move.cpp (0 NOLINT, 7 explicit)
* clang-tidy tests/test_embed/external_module.cpp (0 NOLINT, 1 explicit)
* clang-tidy tests/test_embed/test_interpreter.cpp (0 NOLINT, 1 explicit)
* clang-tidy tests/object.h (0 NOLINT, 2 explicit)
* clang-tidy batch of fully automatic fixes.
* Workaround for MSVC 19.16.27045.0 C++17 Python 2 C++ syntax error.
2021-09-09 01:53:38 +00:00
|
|
|
explicit PythonAlreadySetInDestructor(const py::str &s) : s(s) {}
|
2020-08-08 10:07:14 +00:00
|
|
|
~PythonAlreadySetInDestructor() {
|
|
|
|
py::dict foo;
|
|
|
|
try {
|
|
|
|
// Assign to a py::object to force read access of nonexistent dict entry
|
|
|
|
py::object o = foo["bar"];
|
2022-02-10 20:17:07 +00:00
|
|
|
} catch (py::error_already_set &ex) {
|
2020-08-08 10:07:14 +00:00
|
|
|
ex.discard_as_unraisable(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
py::str s;
|
|
|
|
};
|
|
|
|
|
error_already_set::what() is now constructed lazily (#1895)
* error_already_set::what() is now constructed lazily
Prior to this commit throwing error_already_set was expensive due to the
eager construction of the error string (which required traversing the
Python stack). See #1853 for more context and an alternative take on the
issue.
Note that error_already_set no longer inherits from std::runtime_error
because the latter has no default constructor.
* Do not attempt to normalize if no exception occurred
This is not supported on PyPy-2.7 5.8.0.
* Extract exception name via tp_name
This is faster than dynamically looking up __name__ via GetAttrString.
Note though that the runtime of the code throwing an error_already_set
will be dominated by stack unwinding so the improvement will not be
noticeable.
Before:
396 ns ± 0.913 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
After:
277 ns ± 0.549 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Benchmark:
const std::string foo() {
PyErr_SetString(PyExc_KeyError, "");
const std::string &s = py::detail::error_string();
PyErr_Clear();
return s;
}
PYBIND11_MODULE(foo, m) {
m.def("foo", &::foo);
}
* Reverted error_already_set to subclass std::runtime_error
* Revert "Extract exception name via tp_name"
The implementation of __name__ is slightly more complex than that.
It handles the module name prefix, and heap-allocated types. We could
port it to pybind11 later on but for now it seems like an overkill.
This reverts commit f1435c7e6b068a1ed13ebd3db597ea3bb15aa398.
* Cosmit following @YannickJadoul's comments
Note that detail::error_string() no longer calls PyException_SetTraceback
as it is unncessary for pretty-printing the exception.
* Fixed PyPy build
* Moved normalization to error_already_set ctor
* Fix merge bugs
* Fix more merge errors
* Improve formatting
* Improve error message in rare case
* Revert back if statements
* Fix clang-tidy
* Try removing mutable
* Does build_mode release fix it
* Set to Debug to expose segfault
* Fix remove set error string
* Do not run error_string() more than once
* Trying setting the tracebackk to the value
* guard if m_type is null
* Try to debug PGI
* One last try for PGI
* Does reverting this fix PyPy
* Reviewer suggestions
* Remove unnecessary initialization
* Add noexcept move and explicit fail throw
* Optimize error_string creation
* Fix typo
* Revert noexcept
* Fix merge conflict error
* Abuse assignment operator
* Revert operator abuse
* See if we still need debug
* Remove unnecessary mutable
* Report "FATAL failure building pybind11::error_already_set error_string" and terminate process.
* Try specifying noexcept again
* Try explicit ctor
* default ctor is noexcept too
* Apply reviewer suggestions, simplify code, and make helper method private
* Remove unnecessary include
* Clang-Tidy fix
* detail::obj_class_name(), fprintf with [STDERR], [STDOUT] tags, polish comments
* consistently check m_lazy_what.empty() also in production builds
* Make a comment slightly less ambiguous.
* Bug fix: Remove `what();` from `restore()`.
It sure would need to be guarded by `if (m_type)`, otherwise `what()` fails and masks that no error was set (see update unit test). But since `error_already_set` is copyable, there is no point in releasing m_type, m_value, m_trace, therefore we can just as well avoid the runtime overhead of force-building `m_lazy_what`, it may never be used.
* Replace extremely opaque (unhelpful) error message with a truthful reflection of what we know.
* Fix clang-tidy error [performance-move-constructor-init].
* Make expected error message less specific.
* Various changes.
* bug fix: error_string(PyObject **, ...)
* Putting back the two eager PyErr_NormalizeException() calls.
* Change error_already_set() to call pybind11_fail() if the Python error indicator not set. The net result is that a std::runtime_error is thrown instead of error_already_set, but all tests pass as is.
* Remove mutable (fixes oversight in the previous commit).
* Normalize the exception only locally in error_string(). Python 3.6 & 3.7 test failures expected. This is meant for benchmarking, to determine if it is worth the trouble looking into the failures.
* clang-tidy: use auto
* Use `gil_scoped_acquire_local` in `error_already_set` destructor. See long comment.
* For Python < 3.8: `PyErr_NormalizeException` before `PyErr_WriteUnraisable`
* Go back to replacing the held Python exception with then normalized exception, if & when needed. Consistently document the side-effect.
* Slightly rewording comment. (There were also other failures.)
* Add 1-line comment for obj_class_name()
* Benchmark code, with results in this commit message.
function #calls test time [s] μs / call
master pure_unwind 729540 1.061 14.539876
err_set_unwind_err_clear 681476 1.040 15.260282
err_set_error_already_set 508038 1.049 20.640525
error_already_set_restore 555578 1.052 18.933288
pr1895_original_foo 244113 1.050 43.018168
PR / master
PR #1895 pure_unwind 736981 1.054 14.295685 98.32%
err_set_unwind_err_clear 685820 1.045 15.237399 99.85%
err_set_error_already_set 661374 1.046 15.811879 76.61%
error_already_set_restore 669881 1.048 15.645176 82.63%
pr1895_original_foo 318243 1.059 33.290806 77.39%
master @ commit ad146b2a1877e8ba3803f94a7837969835a297a7
Running tests in directory "/usr/local/google/home/rwgk/forked/pybind11/tests":
============================= test session starts ==============================
platform linux -- Python 3.9.10, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /usr/local/google/home/rwgk/forked/pybind11/tests, configfile: pytest.ini
collecting ... collected 5 items
test_perf_error_already_set.py::test_perf[pure_unwind]
PERF pure_unwind,729540,1.061,14.539876
PASSED
test_perf_error_already_set.py::test_perf[err_set_unwind_err_clear]
PERF err_set_unwind_err_clear,681476,1.040,15.260282
PASSED
test_perf_error_already_set.py::test_perf[err_set_error_already_set]
PERF err_set_error_already_set,508038,1.049,20.640525
PASSED
test_perf_error_already_set.py::test_perf[error_already_set_restore]
PERF error_already_set_restore,555578,1.052,18.933288
PASSED
test_perf_error_already_set.py::test_perf[pr1895_original_foo]
PERF pr1895_original_foo,244113,1.050,43.018168
PASSED
============================== 5 passed in 12.38s ==============================
pr1895 @ commit 8dff51d12e4af11aff415ee966070368fe606664
Running tests in directory "/usr/local/google/home/rwgk/forked/pybind11/tests":
============================= test session starts ==============================
platform linux -- Python 3.9.10, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /usr/local/google/home/rwgk/forked/pybind11/tests, configfile: pytest.ini
collecting ... collected 5 items
test_perf_error_already_set.py::test_perf[pure_unwind]
PERF pure_unwind,736981,1.054,14.295685
PASSED
test_perf_error_already_set.py::test_perf[err_set_unwind_err_clear]
PERF err_set_unwind_err_clear,685820,1.045,15.237399
PASSED
test_perf_error_already_set.py::test_perf[err_set_error_already_set]
PERF err_set_error_already_set,661374,1.046,15.811879
PASSED
test_perf_error_already_set.py::test_perf[error_already_set_restore]
PERF error_already_set_restore,669881,1.048,15.645176
PASSED
test_perf_error_already_set.py::test_perf[pr1895_original_foo]
PERF pr1895_original_foo,318243,1.059,33.290806
PASSED
============================== 5 passed in 12.40s ==============================
clang++ -o pybind11/tests/test_perf_error_already_set.os -c -std=c++17 -fPIC -fvisibility=hidden -Os -flto -Wall -Wextra -Wconversion -Wcast-qual -Wdeprecated -Wnon-virtual-dtor -Wunused-result -isystem /usr/include/python3.9 -isystem /usr/include/eigen3 -DPYBIND11_STRICT_ASSERTS_CLASS_HOLDER_VS_TYPE_CASTER_MIX -DPYBIND11_TEST_BOOST -Ipybind11/include -I/usr/local/google/home/rwgk/forked/pybind11/include -I/usr/local/google/home/rwgk/clone/pybind11/include /usr/local/google/home/rwgk/forked/pybind11/tests/test_perf_error_already_set.cpp
clang++ -o lib/pybind11_tests.so -shared -fPIC -Os -flto -shared ...
Debian clang version 13.0.1-3+build2
Target: x86_64-pc-linux-gnu
Thread model: posix
* Changing call_repetitions_target_elapsed_secs to 0.1 for regular unit testing.
* Adding in `recursion_depth`
* Optimized ctor
* Fix silly bug in recurse_first_then_call()
* Add tests that have equivalent PyErr_Fetch(), PyErr_Restore() but no try-catch.
* Add call_error_string to tests. Sample only recursion_depth 0, 100.
* Show lazy-what speed-up in percent.
* Include real_work in benchmarks.
* Replace all PyErr_SetString() with generate_python_exception_with_traceback()
* Better organization of test loops.
* Add test_error_already_set_copy_move
* Fix bug in newly added test (discovered by clang-tidy): actually use move ctor
* MSVC detects the unreachable return
* change test_perf_error_already_set.py back to quick mode
* Inherit from std::exception (instead of std::runtime_error, which does not make sense anymore with the lazy what)
* Special handling under Windows.
* print with leading newline
* Removing test_perf_error_already_set (copies are under https://github.com/rwgk/rwgk_tbx/commit/7765113fbb659e1ea004c5ba24fb578244bc6cfd).
* Avoid gil and scope overhead if there is nothing to release.
* Restore default move ctor. "member function" instead of "function" (note that "method" is Python terminology).
* Delete error_already_set copy ctor.
* Make restore() non-const again to resolve clang-tidy failure (still experimenting).
* Bring back error_already_set copy ctor, to see if that resolves the 4 MSVC test failures.
* Add noexcept to error_already_set copy & move ctors (as suggested by @skylion007 IIUC).
* Trying one-by-one noexcept copy ctor for old compilers.
* Add back test covering copy ctor. Add another simple test that exercises the copy ctor.
* Exclude more older compilers from using the noexcept = default ctors. (The tests in the previous commit exposed that those are broken.)
* Factor out & reuse gil_scoped_acquire_local as gil_scoped_acquire_simple
* Guard gil_scoped_acquire_simple by _Py_IsFinalizing() check.
* what() GIL safety
* clang-tidy & Python 3.6 fixes
* Use `gil_scoped_acquire` in dtor, copy ctor, `what()`. Remove `_Py_IsFinalizing()` checks (they are racy: https://github.com/python/cpython/pull/28525).
* Remove error_scope from copy ctor.
* Add `error_scope` to `get_internals()`, to cover the situation that `get_internals()` is called from the `error_already_set` dtor while a new Python error is in flight already. Also backing out `gil_scoped_acquire_simple` change.
* Add `FlakyException` tests with failure triggers in `__init__` and `__str__`
THIS IS STILL A WORK IN PROGRESS. This commit is only an important resting point.
This commit is a first attempt at addressing the observation that `PyErr_NormalizeException()` completely replaces the original exception if `__init__` fails. This can be very confusing even in small applications, and extremely confusing in large ones.
* Tweaks to resolve Py 3.6 and PyPy CI failures.
* Normalize Python exception immediately in error_already_set ctor.
For background see: https://github.com/pybind/pybind11/pull/1895#issuecomment-1135304081
* Fix oversights based on CI failures (copy & move ctor initialization).
* Move @pytest.mark.xfail("env.PYPY") after @pytest.mark.parametrize(...)
* Use @pytest.mark.skipif (xfail does not work for segfaults, of course).
* Remove unused obj_class_name_or() function (it was added only under this PR).
* Remove already obsolete C++ comments and code that were added only under this PR.
* Slightly better (newly added) comments.
* Factor out detail::error_fetch_and_normalize. Preparation for producing identical results from error_already_set::what() and detail::error_string(). Note that this is a very conservative refactoring. It would be much better to first move detail::error_string into detail/error_string.h
* Copy most of error_string() code to new error_fetch_and_normalize::complete_lazy_error_string()
* Remove all error_string() code from detail/type_caster_base.h. Note that this commit includes a subtle bug fix: previously error_string() restored the Python error, which will upset pybind11_fail(). This never was a problem in practice because the two PyType_Ready() calls in detail/class.h do not usually fail.
* Return const std::string& instead of const char * and move error_string() to pytypes.h
* Remove gil_scope_acquire from error_fetch_and_normalize, add back to error_already_set
* Better handling of FlakyException __str__ failure.
* Move error_fetch_and_normalize::complete_lazy_error_string() implementation from pybind11.h to pytypes.h
* Add error_fetch_and_normalize::release_py_object_references() and use from error_already_set dtor.
* Use shared_ptr for m_fetched_error => 1. non-racy, copy ctor that does not need the GIL; 2. enables guard against duplicate restore() calls.
* Add comments.
* Trivial renaming of a newly introduced member function.
* Workaround for PyPy
* Bug fix (oversight). Only valgrind got this one.
* Use shared_ptr custom deleter for m_fetched_error in error_already_set. This enables removing the dtor, copy ctor, move ctor completely.
* Further small simplification. With the GIL held, simply deleting the raw_ptr takes care of everything.
* IWYU cleanup
```
iwyu version: include-what-you-use 0.17 based on Debian clang version 13.0.1-3+build2
```
Command used:
```
iwyu -c -std=c++17 -DPYBIND11_TEST_BOOST -Iinclude/pybind11 -I/usr/include/python3.9 -I/usr/include/eigen3 include/pybind11/pytypes.cpp
```
pytypes.cpp is a temporary file: `#include "pytypes.h"`
The raw output is very long and noisy.
I decided to use `#include <cstddef>` instead of `#include <cstdio>` for `std::size_t` (iwyu sticks to the manual choice).
I ignored all iwyu suggestions that are indirectly covered by `#include <Python.h>`.
I manually verified that all added includes are actually needed.
Co-authored-by: Aaron Gokaslan <skylion.aaron@gmail.com>
Co-authored-by: Ralf W. Grosse-Kunstleve <rwgk@google.com>
2022-06-02 23:17:38 +00:00
|
|
|
std::string error_already_set_what(const py::object &exc_type, const py::object &exc_value) {
|
|
|
|
PyErr_SetObject(exc_type.ptr(), exc_value.ptr());
|
|
|
|
return py::error_already_set().what();
|
|
|
|
}
|
|
|
|
|
2017-07-23 16:26:17 +00:00
|
|
|
TEST_SUBMODULE(exceptions, m) {
|
2022-02-10 20:17:07 +00:00
|
|
|
m.def("throw_std_exception",
|
|
|
|
[]() { throw std::runtime_error("This exception was intentionally thrown."); });
|
2017-06-08 22:44:49 +00:00
|
|
|
|
2016-06-17 21:35:59 +00:00
|
|
|
// make a new custom exception and use it as a translation target
|
|
|
|
static py::exception<MyException> ex(m, "MyException");
|
|
|
|
py::register_exception_translator([](std::exception_ptr p) {
|
|
|
|
try {
|
2022-02-08 00:23:20 +00:00
|
|
|
if (p) {
|
|
|
|
std::rethrow_exception(p);
|
|
|
|
}
|
2016-06-17 21:35:59 +00:00
|
|
|
} catch (const MyException &e) {
|
2016-09-16 06:04:15 +00:00
|
|
|
// Set MyException as the active python error
|
|
|
|
ex(e.what());
|
2016-06-17 21:35:59 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// register new translator for MyException2
|
|
|
|
// no need to store anything here because this type will
|
|
|
|
// never by visible from Python
|
|
|
|
py::register_exception_translator([](std::exception_ptr p) {
|
|
|
|
try {
|
2022-02-08 00:23:20 +00:00
|
|
|
if (p) {
|
|
|
|
std::rethrow_exception(p);
|
|
|
|
}
|
2016-06-17 21:35:59 +00:00
|
|
|
} catch (const MyException2 &e) {
|
2016-09-16 06:04:15 +00:00
|
|
|
// Translate this exception to a standard RuntimeError
|
2016-06-17 21:35:59 +00:00
|
|
|
PyErr_SetString(PyExc_RuntimeError, e.what());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// register new translator for MyException4
|
|
|
|
// which will catch it and delegate to the previously registered
|
|
|
|
// translator for MyException by throwing a new exception
|
|
|
|
py::register_exception_translator([](std::exception_ptr p) {
|
|
|
|
try {
|
2022-02-08 00:23:20 +00:00
|
|
|
if (p) {
|
|
|
|
std::rethrow_exception(p);
|
|
|
|
}
|
2016-06-17 21:35:59 +00:00
|
|
|
} catch (const MyException4 &e) {
|
|
|
|
throw MyException(e.what());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-09-16 06:04:15 +00:00
|
|
|
// A simple exception translation:
|
|
|
|
auto ex5 = py::register_exception<MyException5>(m, "MyException5");
|
|
|
|
// A slightly more complicated one that declares MyException5_1 as a subclass of MyException5
|
|
|
|
py::register_exception<MyException5_1>(m, "MyException5_1", ex5.ptr());
|
|
|
|
|
2022-02-10 20:17:07 +00:00
|
|
|
// py::register_local_exception<LocalSimpleException>(m, "LocalSimpleException")
|
2021-07-21 12:22:18 +00:00
|
|
|
|
|
|
|
py::register_local_exception_translator([](std::exception_ptr p) {
|
2022-02-10 20:17:07 +00:00
|
|
|
try {
|
|
|
|
if (p) {
|
|
|
|
std::rethrow_exception(p);
|
|
|
|
}
|
|
|
|
} catch (const MyException6 &e) {
|
|
|
|
PyErr_SetString(PyExc_RuntimeError, e.what());
|
|
|
|
}
|
2021-07-21 12:22:18 +00:00
|
|
|
});
|
|
|
|
|
2017-07-23 16:26:17 +00:00
|
|
|
m.def("throws1", []() { throw MyException("this error should go to a custom type"); });
|
2022-02-10 20:17:07 +00:00
|
|
|
m.def("throws2",
|
|
|
|
[]() { throw MyException2("this error should go to a standard Python exception"); });
|
2017-07-23 16:26:17 +00:00
|
|
|
m.def("throws3", []() { throw MyException3("this error cannot be translated"); });
|
|
|
|
m.def("throws4", []() { throw MyException4("this error is rethrown"); });
|
2022-02-10 20:17:07 +00:00
|
|
|
m.def("throws5",
|
|
|
|
[]() { throw MyException5("this is a helper-defined translated exception"); });
|
2017-07-23 16:26:17 +00:00
|
|
|
m.def("throws5_1", []() { throw MyException5_1("MyException5 subclass"); });
|
2021-07-21 12:22:18 +00:00
|
|
|
m.def("throws6", []() { throw MyException6("MyException6 only handled in this module"); });
|
2022-02-10 20:17:07 +00:00
|
|
|
m.def("throws_logic_error", []() {
|
|
|
|
throw std::logic_error("this error should fall through to the standard handler");
|
|
|
|
});
|
2021-07-21 12:22:18 +00:00
|
|
|
m.def("throws_overflow_error", []() { throw std::overflow_error(""); });
|
|
|
|
m.def("throws_local_error", []() { throw LocalException("never caught"); });
|
|
|
|
m.def("throws_local_simple_error", []() { throw LocalSimpleException("this mod"); });
|
2017-07-23 16:26:17 +00:00
|
|
|
m.def("exception_matches", []() {
|
|
|
|
py::dict foo;
|
2019-05-12 21:35:49 +00:00
|
|
|
try {
|
|
|
|
// Assign to a py::object to force read access of nonexistent dict entry
|
|
|
|
py::object o = foo["bar"];
|
2022-02-10 20:17:07 +00:00
|
|
|
} catch (py::error_already_set &ex) {
|
2022-02-08 00:23:20 +00:00
|
|
|
if (!ex.matches(PyExc_KeyError)) {
|
|
|
|
throw;
|
|
|
|
}
|
2019-05-12 21:35:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
m.def("exception_matches_base", []() {
|
|
|
|
py::dict foo;
|
|
|
|
try {
|
|
|
|
// Assign to a py::object to force read access of nonexistent dict entry
|
|
|
|
py::object o = foo["bar"];
|
2022-02-10 20:17:07 +00:00
|
|
|
} catch (py::error_already_set &ex) {
|
2022-02-08 00:23:20 +00:00
|
|
|
if (!ex.matches(PyExc_Exception)) {
|
|
|
|
throw;
|
|
|
|
}
|
2019-05-12 21:35:49 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
m.def("modulenotfound_exception_matches_base", []() {
|
|
|
|
try {
|
|
|
|
// On Python >= 3.6, this raises a ModuleNotFoundError, a subclass of ImportError
|
2020-10-03 17:38:03 +00:00
|
|
|
py::module_::import("nonexistent");
|
2022-02-10 20:17:07 +00:00
|
|
|
} catch (py::error_already_set &ex) {
|
2022-02-08 00:23:20 +00:00
|
|
|
if (!ex.matches(PyExc_ImportError)) {
|
|
|
|
throw;
|
|
|
|
}
|
2019-05-12 21:35:49 +00:00
|
|
|
return true;
|
2017-07-23 16:26:17 +00:00
|
|
|
}
|
2019-05-12 21:35:49 +00:00
|
|
|
return false;
|
2017-07-23 16:26:17 +00:00
|
|
|
});
|
2016-06-17 21:35:59 +00:00
|
|
|
|
2016-09-07 20:10:16 +00:00
|
|
|
m.def("throw_already_set", [](bool err) {
|
2022-02-08 00:23:20 +00:00
|
|
|
if (err) {
|
2016-09-07 20:10:16 +00:00
|
|
|
PyErr_SetString(PyExc_ValueError, "foo");
|
2022-02-08 00:23:20 +00:00
|
|
|
}
|
2016-09-07 20:10:16 +00:00
|
|
|
try {
|
|
|
|
throw py::error_already_set();
|
2022-02-10 20:17:07 +00:00
|
|
|
} catch (const std::runtime_error &e) {
|
|
|
|
if ((err && e.what() != std::string("ValueError: foo"))
|
2022-05-31 18:54:33 +00:00
|
|
|
|| (!err
|
|
|
|
&& e.what()
|
|
|
|
!= std::string("Internal error: pybind11::error_already_set called "
|
|
|
|
"while Python error indicator not set."))) {
|
2016-09-07 20:10:16 +00:00
|
|
|
PyErr_Clear();
|
|
|
|
throw std::runtime_error("error message mismatch");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
PyErr_Clear();
|
2022-02-08 00:23:20 +00:00
|
|
|
if (err) {
|
2016-09-07 20:10:16 +00:00
|
|
|
PyErr_SetString(PyExc_ValueError, "foo");
|
2022-02-08 00:23:20 +00:00
|
|
|
}
|
2016-09-07 20:10:16 +00:00
|
|
|
throw py::error_already_set();
|
|
|
|
});
|
2016-09-10 09:58:02 +00:00
|
|
|
|
2021-07-12 20:10:28 +00:00
|
|
|
m.def("python_call_in_destructor", [](const py::dict &d) {
|
|
|
|
bool retval = false;
|
2016-09-10 09:58:02 +00:00
|
|
|
try {
|
|
|
|
PythonCallInDestructor set_dict_in_destructor(d);
|
|
|
|
PyErr_SetString(PyExc_ValueError, "foo");
|
|
|
|
throw py::error_already_set();
|
2022-02-10 20:17:07 +00:00
|
|
|
} catch (const py::error_already_set &) {
|
2021-07-12 20:10:28 +00:00
|
|
|
retval = true;
|
2016-09-10 09:58:02 +00:00
|
|
|
}
|
2021-07-12 20:10:28 +00:00
|
|
|
return retval;
|
2016-09-10 09:58:02 +00:00
|
|
|
});
|
Simplify error_already_set
`error_already_set` is more complicated than it needs to be, partly
because it manages reference counts itself rather than using
`py::object`, and partly because it tries to do more exception clearing
than is needed. This commit greatly simplifies it, and fixes #927.
Using `py::object` instead of `PyObject *` means we can rely on
implicit copy/move constructors.
The current logic did both a `PyErr_Clear` on deletion *and* a
`PyErr_Fetch` on creation. I can't see how the `PyErr_Clear` on
deletion is ever useful: the `Fetch` on creation itself clears the
error, so the only way doing a `PyErr_Clear` on deletion could do
anything if is some *other* exception was raised while the
`error_already_set` object was alive--but in that case, clearing some
other exception seems wrong. (Code that is worried about an exception
handler raising another exception would already catch a second
`error_already_set` from exception code).
The destructor itself called `clear()`, but `clear()` was a little bit
more paranoid that needed: it called `restore()` to restore the
currently captured error, but then immediately cleared it, using the
`PyErr_Restore` to release the references. That's unnecessary: it's
valid for us to release the references manually. This updates the code
to simply release the references on the three objects (preserving the
gil acquire).
`clear()`, however, also had the side effect of clearing the current
error, even if the current `error_already_set` didn't have a current
error (e.g. because of a previous `restore()` or `clear()` call). I
don't really see how clearing the error here can ever actually be
useful: the only way the current error could be set is if you called
`restore()` (in which case the current stored error-related members have
already been released), or if some *other* code raised the error, in
which case `clear()` on *this* object is clearing an error for which it
shouldn't be responsible.
Neither of those seem like intentional or desirable features, and
manually requesting deletion of the stored references similarly seems
pointless, so I've just made `clear()` an empty method and marked it
deprecated.
This also fixes a minor potential issue with the destruction: it is
technically possible for `value` to be null (though this seems likely to
be rare in practice); this updates the check to look at `type` which
will always be non-null for a `Fetch`ed exception.
This also adds error_already_set round-trip throw tests to the test
suite.
2017-07-21 03:14:33 +00:00
|
|
|
|
2021-06-22 16:11:54 +00:00
|
|
|
m.def("python_alreadyset_in_destructor", [](const py::str &s) {
|
2020-08-08 10:07:14 +00:00
|
|
|
PythonAlreadySetInDestructor alreadyset_in_destructor(s);
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
Simplify error_already_set
`error_already_set` is more complicated than it needs to be, partly
because it manages reference counts itself rather than using
`py::object`, and partly because it tries to do more exception clearing
than is needed. This commit greatly simplifies it, and fixes #927.
Using `py::object` instead of `PyObject *` means we can rely on
implicit copy/move constructors.
The current logic did both a `PyErr_Clear` on deletion *and* a
`PyErr_Fetch` on creation. I can't see how the `PyErr_Clear` on
deletion is ever useful: the `Fetch` on creation itself clears the
error, so the only way doing a `PyErr_Clear` on deletion could do
anything if is some *other* exception was raised while the
`error_already_set` object was alive--but in that case, clearing some
other exception seems wrong. (Code that is worried about an exception
handler raising another exception would already catch a second
`error_already_set` from exception code).
The destructor itself called `clear()`, but `clear()` was a little bit
more paranoid that needed: it called `restore()` to restore the
currently captured error, but then immediately cleared it, using the
`PyErr_Restore` to release the references. That's unnecessary: it's
valid for us to release the references manually. This updates the code
to simply release the references on the three objects (preserving the
gil acquire).
`clear()`, however, also had the side effect of clearing the current
error, even if the current `error_already_set` didn't have a current
error (e.g. because of a previous `restore()` or `clear()` call). I
don't really see how clearing the error here can ever actually be
useful: the only way the current error could be set is if you called
`restore()` (in which case the current stored error-related members have
already been released), or if some *other* code raised the error, in
which case `clear()` on *this* object is clearing an error for which it
shouldn't be responsible.
Neither of those seem like intentional or desirable features, and
manually requesting deletion of the stored references similarly seems
pointless, so I've just made `clear()` an empty method and marked it
deprecated.
This also fixes a minor potential issue with the destruction: it is
technically possible for `value` to be null (though this seems likely to
be rare in practice); this updates the check to look at `type` which
will always be non-null for a `Fetch`ed exception.
This also adds error_already_set round-trip throw tests to the test
suite.
2017-07-21 03:14:33 +00:00
|
|
|
// test_nested_throws
|
2021-06-22 16:11:54 +00:00
|
|
|
m.def("try_catch",
|
|
|
|
[m](const py::object &exc_type, const py::function &f, const py::args &args) {
|
|
|
|
try {
|
|
|
|
f(*args);
|
|
|
|
} catch (py::error_already_set &ex) {
|
2022-02-08 00:23:20 +00:00
|
|
|
if (ex.matches(exc_type)) {
|
2021-06-22 16:11:54 +00:00
|
|
|
py::print(ex.what());
|
2022-02-08 00:23:20 +00:00
|
|
|
} else {
|
error_already_set::what() is now constructed lazily (#1895)
* error_already_set::what() is now constructed lazily
Prior to this commit throwing error_already_set was expensive due to the
eager construction of the error string (which required traversing the
Python stack). See #1853 for more context and an alternative take on the
issue.
Note that error_already_set no longer inherits from std::runtime_error
because the latter has no default constructor.
* Do not attempt to normalize if no exception occurred
This is not supported on PyPy-2.7 5.8.0.
* Extract exception name via tp_name
This is faster than dynamically looking up __name__ via GetAttrString.
Note though that the runtime of the code throwing an error_already_set
will be dominated by stack unwinding so the improvement will not be
noticeable.
Before:
396 ns ± 0.913 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
After:
277 ns ± 0.549 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Benchmark:
const std::string foo() {
PyErr_SetString(PyExc_KeyError, "");
const std::string &s = py::detail::error_string();
PyErr_Clear();
return s;
}
PYBIND11_MODULE(foo, m) {
m.def("foo", &::foo);
}
* Reverted error_already_set to subclass std::runtime_error
* Revert "Extract exception name via tp_name"
The implementation of __name__ is slightly more complex than that.
It handles the module name prefix, and heap-allocated types. We could
port it to pybind11 later on but for now it seems like an overkill.
This reverts commit f1435c7e6b068a1ed13ebd3db597ea3bb15aa398.
* Cosmit following @YannickJadoul's comments
Note that detail::error_string() no longer calls PyException_SetTraceback
as it is unncessary for pretty-printing the exception.
* Fixed PyPy build
* Moved normalization to error_already_set ctor
* Fix merge bugs
* Fix more merge errors
* Improve formatting
* Improve error message in rare case
* Revert back if statements
* Fix clang-tidy
* Try removing mutable
* Does build_mode release fix it
* Set to Debug to expose segfault
* Fix remove set error string
* Do not run error_string() more than once
* Trying setting the tracebackk to the value
* guard if m_type is null
* Try to debug PGI
* One last try for PGI
* Does reverting this fix PyPy
* Reviewer suggestions
* Remove unnecessary initialization
* Add noexcept move and explicit fail throw
* Optimize error_string creation
* Fix typo
* Revert noexcept
* Fix merge conflict error
* Abuse assignment operator
* Revert operator abuse
* See if we still need debug
* Remove unnecessary mutable
* Report "FATAL failure building pybind11::error_already_set error_string" and terminate process.
* Try specifying noexcept again
* Try explicit ctor
* default ctor is noexcept too
* Apply reviewer suggestions, simplify code, and make helper method private
* Remove unnecessary include
* Clang-Tidy fix
* detail::obj_class_name(), fprintf with [STDERR], [STDOUT] tags, polish comments
* consistently check m_lazy_what.empty() also in production builds
* Make a comment slightly less ambiguous.
* Bug fix: Remove `what();` from `restore()`.
It sure would need to be guarded by `if (m_type)`, otherwise `what()` fails and masks that no error was set (see update unit test). But since `error_already_set` is copyable, there is no point in releasing m_type, m_value, m_trace, therefore we can just as well avoid the runtime overhead of force-building `m_lazy_what`, it may never be used.
* Replace extremely opaque (unhelpful) error message with a truthful reflection of what we know.
* Fix clang-tidy error [performance-move-constructor-init].
* Make expected error message less specific.
* Various changes.
* bug fix: error_string(PyObject **, ...)
* Putting back the two eager PyErr_NormalizeException() calls.
* Change error_already_set() to call pybind11_fail() if the Python error indicator not set. The net result is that a std::runtime_error is thrown instead of error_already_set, but all tests pass as is.
* Remove mutable (fixes oversight in the previous commit).
* Normalize the exception only locally in error_string(). Python 3.6 & 3.7 test failures expected. This is meant for benchmarking, to determine if it is worth the trouble looking into the failures.
* clang-tidy: use auto
* Use `gil_scoped_acquire_local` in `error_already_set` destructor. See long comment.
* For Python < 3.8: `PyErr_NormalizeException` before `PyErr_WriteUnraisable`
* Go back to replacing the held Python exception with then normalized exception, if & when needed. Consistently document the side-effect.
* Slightly rewording comment. (There were also other failures.)
* Add 1-line comment for obj_class_name()
* Benchmark code, with results in this commit message.
function #calls test time [s] μs / call
master pure_unwind 729540 1.061 14.539876
err_set_unwind_err_clear 681476 1.040 15.260282
err_set_error_already_set 508038 1.049 20.640525
error_already_set_restore 555578 1.052 18.933288
pr1895_original_foo 244113 1.050 43.018168
PR / master
PR #1895 pure_unwind 736981 1.054 14.295685 98.32%
err_set_unwind_err_clear 685820 1.045 15.237399 99.85%
err_set_error_already_set 661374 1.046 15.811879 76.61%
error_already_set_restore 669881 1.048 15.645176 82.63%
pr1895_original_foo 318243 1.059 33.290806 77.39%
master @ commit ad146b2a1877e8ba3803f94a7837969835a297a7
Running tests in directory "/usr/local/google/home/rwgk/forked/pybind11/tests":
============================= test session starts ==============================
platform linux -- Python 3.9.10, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /usr/local/google/home/rwgk/forked/pybind11/tests, configfile: pytest.ini
collecting ... collected 5 items
test_perf_error_already_set.py::test_perf[pure_unwind]
PERF pure_unwind,729540,1.061,14.539876
PASSED
test_perf_error_already_set.py::test_perf[err_set_unwind_err_clear]
PERF err_set_unwind_err_clear,681476,1.040,15.260282
PASSED
test_perf_error_already_set.py::test_perf[err_set_error_already_set]
PERF err_set_error_already_set,508038,1.049,20.640525
PASSED
test_perf_error_already_set.py::test_perf[error_already_set_restore]
PERF error_already_set_restore,555578,1.052,18.933288
PASSED
test_perf_error_already_set.py::test_perf[pr1895_original_foo]
PERF pr1895_original_foo,244113,1.050,43.018168
PASSED
============================== 5 passed in 12.38s ==============================
pr1895 @ commit 8dff51d12e4af11aff415ee966070368fe606664
Running tests in directory "/usr/local/google/home/rwgk/forked/pybind11/tests":
============================= test session starts ==============================
platform linux -- Python 3.9.10, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /usr/local/google/home/rwgk/forked/pybind11/tests, configfile: pytest.ini
collecting ... collected 5 items
test_perf_error_already_set.py::test_perf[pure_unwind]
PERF pure_unwind,736981,1.054,14.295685
PASSED
test_perf_error_already_set.py::test_perf[err_set_unwind_err_clear]
PERF err_set_unwind_err_clear,685820,1.045,15.237399
PASSED
test_perf_error_already_set.py::test_perf[err_set_error_already_set]
PERF err_set_error_already_set,661374,1.046,15.811879
PASSED
test_perf_error_already_set.py::test_perf[error_already_set_restore]
PERF error_already_set_restore,669881,1.048,15.645176
PASSED
test_perf_error_already_set.py::test_perf[pr1895_original_foo]
PERF pr1895_original_foo,318243,1.059,33.290806
PASSED
============================== 5 passed in 12.40s ==============================
clang++ -o pybind11/tests/test_perf_error_already_set.os -c -std=c++17 -fPIC -fvisibility=hidden -Os -flto -Wall -Wextra -Wconversion -Wcast-qual -Wdeprecated -Wnon-virtual-dtor -Wunused-result -isystem /usr/include/python3.9 -isystem /usr/include/eigen3 -DPYBIND11_STRICT_ASSERTS_CLASS_HOLDER_VS_TYPE_CASTER_MIX -DPYBIND11_TEST_BOOST -Ipybind11/include -I/usr/local/google/home/rwgk/forked/pybind11/include -I/usr/local/google/home/rwgk/clone/pybind11/include /usr/local/google/home/rwgk/forked/pybind11/tests/test_perf_error_already_set.cpp
clang++ -o lib/pybind11_tests.so -shared -fPIC -Os -flto -shared ...
Debian clang version 13.0.1-3+build2
Target: x86_64-pc-linux-gnu
Thread model: posix
* Changing call_repetitions_target_elapsed_secs to 0.1 for regular unit testing.
* Adding in `recursion_depth`
* Optimized ctor
* Fix silly bug in recurse_first_then_call()
* Add tests that have equivalent PyErr_Fetch(), PyErr_Restore() but no try-catch.
* Add call_error_string to tests. Sample only recursion_depth 0, 100.
* Show lazy-what speed-up in percent.
* Include real_work in benchmarks.
* Replace all PyErr_SetString() with generate_python_exception_with_traceback()
* Better organization of test loops.
* Add test_error_already_set_copy_move
* Fix bug in newly added test (discovered by clang-tidy): actually use move ctor
* MSVC detects the unreachable return
* change test_perf_error_already_set.py back to quick mode
* Inherit from std::exception (instead of std::runtime_error, which does not make sense anymore with the lazy what)
* Special handling under Windows.
* print with leading newline
* Removing test_perf_error_already_set (copies are under https://github.com/rwgk/rwgk_tbx/commit/7765113fbb659e1ea004c5ba24fb578244bc6cfd).
* Avoid gil and scope overhead if there is nothing to release.
* Restore default move ctor. "member function" instead of "function" (note that "method" is Python terminology).
* Delete error_already_set copy ctor.
* Make restore() non-const again to resolve clang-tidy failure (still experimenting).
* Bring back error_already_set copy ctor, to see if that resolves the 4 MSVC test failures.
* Add noexcept to error_already_set copy & move ctors (as suggested by @skylion007 IIUC).
* Trying one-by-one noexcept copy ctor for old compilers.
* Add back test covering copy ctor. Add another simple test that exercises the copy ctor.
* Exclude more older compilers from using the noexcept = default ctors. (The tests in the previous commit exposed that those are broken.)
* Factor out & reuse gil_scoped_acquire_local as gil_scoped_acquire_simple
* Guard gil_scoped_acquire_simple by _Py_IsFinalizing() check.
* what() GIL safety
* clang-tidy & Python 3.6 fixes
* Use `gil_scoped_acquire` in dtor, copy ctor, `what()`. Remove `_Py_IsFinalizing()` checks (they are racy: https://github.com/python/cpython/pull/28525).
* Remove error_scope from copy ctor.
* Add `error_scope` to `get_internals()`, to cover the situation that `get_internals()` is called from the `error_already_set` dtor while a new Python error is in flight already. Also backing out `gil_scoped_acquire_simple` change.
* Add `FlakyException` tests with failure triggers in `__init__` and `__str__`
THIS IS STILL A WORK IN PROGRESS. This commit is only an important resting point.
This commit is a first attempt at addressing the observation that `PyErr_NormalizeException()` completely replaces the original exception if `__init__` fails. This can be very confusing even in small applications, and extremely confusing in large ones.
* Tweaks to resolve Py 3.6 and PyPy CI failures.
* Normalize Python exception immediately in error_already_set ctor.
For background see: https://github.com/pybind/pybind11/pull/1895#issuecomment-1135304081
* Fix oversights based on CI failures (copy & move ctor initialization).
* Move @pytest.mark.xfail("env.PYPY") after @pytest.mark.parametrize(...)
* Use @pytest.mark.skipif (xfail does not work for segfaults, of course).
* Remove unused obj_class_name_or() function (it was added only under this PR).
* Remove already obsolete C++ comments and code that were added only under this PR.
* Slightly better (newly added) comments.
* Factor out detail::error_fetch_and_normalize. Preparation for producing identical results from error_already_set::what() and detail::error_string(). Note that this is a very conservative refactoring. It would be much better to first move detail::error_string into detail/error_string.h
* Copy most of error_string() code to new error_fetch_and_normalize::complete_lazy_error_string()
* Remove all error_string() code from detail/type_caster_base.h. Note that this commit includes a subtle bug fix: previously error_string() restored the Python error, which will upset pybind11_fail(). This never was a problem in practice because the two PyType_Ready() calls in detail/class.h do not usually fail.
* Return const std::string& instead of const char * and move error_string() to pytypes.h
* Remove gil_scope_acquire from error_fetch_and_normalize, add back to error_already_set
* Better handling of FlakyException __str__ failure.
* Move error_fetch_and_normalize::complete_lazy_error_string() implementation from pybind11.h to pytypes.h
* Add error_fetch_and_normalize::release_py_object_references() and use from error_already_set dtor.
* Use shared_ptr for m_fetched_error => 1. non-racy, copy ctor that does not need the GIL; 2. enables guard against duplicate restore() calls.
* Add comments.
* Trivial renaming of a newly introduced member function.
* Workaround for PyPy
* Bug fix (oversight). Only valgrind got this one.
* Use shared_ptr custom deleter for m_fetched_error in error_already_set. This enables removing the dtor, copy ctor, move ctor completely.
* Further small simplification. With the GIL held, simply deleting the raw_ptr takes care of everything.
* IWYU cleanup
```
iwyu version: include-what-you-use 0.17 based on Debian clang version 13.0.1-3+build2
```
Command used:
```
iwyu -c -std=c++17 -DPYBIND11_TEST_BOOST -Iinclude/pybind11 -I/usr/include/python3.9 -I/usr/include/eigen3 include/pybind11/pytypes.cpp
```
pytypes.cpp is a temporary file: `#include "pytypes.h"`
The raw output is very long and noisy.
I decided to use `#include <cstddef>` instead of `#include <cstdio>` for `std::size_t` (iwyu sticks to the manual choice).
I ignored all iwyu suggestions that are indirectly covered by `#include <Python.h>`.
I manually verified that all added includes are actually needed.
Co-authored-by: Aaron Gokaslan <skylion.aaron@gmail.com>
Co-authored-by: Ralf W. Grosse-Kunstleve <rwgk@google.com>
2022-06-02 23:17:38 +00:00
|
|
|
// Simply `throw;` also works and is better, but using `throw ex;`
|
|
|
|
// here to cover that situation (as observed in the wild).
|
|
|
|
throw ex; // Invokes the copy ctor.
|
2022-02-08 00:23:20 +00:00
|
|
|
}
|
2021-06-22 16:11:54 +00:00
|
|
|
}
|
|
|
|
});
|
Simplify error_already_set
`error_already_set` is more complicated than it needs to be, partly
because it manages reference counts itself rather than using
`py::object`, and partly because it tries to do more exception clearing
than is needed. This commit greatly simplifies it, and fixes #927.
Using `py::object` instead of `PyObject *` means we can rely on
implicit copy/move constructors.
The current logic did both a `PyErr_Clear` on deletion *and* a
`PyErr_Fetch` on creation. I can't see how the `PyErr_Clear` on
deletion is ever useful: the `Fetch` on creation itself clears the
error, so the only way doing a `PyErr_Clear` on deletion could do
anything if is some *other* exception was raised while the
`error_already_set` object was alive--but in that case, clearing some
other exception seems wrong. (Code that is worried about an exception
handler raising another exception would already catch a second
`error_already_set` from exception code).
The destructor itself called `clear()`, but `clear()` was a little bit
more paranoid that needed: it called `restore()` to restore the
currently captured error, but then immediately cleared it, using the
`PyErr_Restore` to release the references. That's unnecessary: it's
valid for us to release the references manually. This updates the code
to simply release the references on the three objects (preserving the
gil acquire).
`clear()`, however, also had the side effect of clearing the current
error, even if the current `error_already_set` didn't have a current
error (e.g. because of a previous `restore()` or `clear()` call). I
don't really see how clearing the error here can ever actually be
useful: the only way the current error could be set is if you called
`restore()` (in which case the current stored error-related members have
already been released), or if some *other* code raised the error, in
which case `clear()` on *this* object is clearing an error for which it
shouldn't be responsible.
Neither of those seem like intentional or desirable features, and
manually requesting deletion of the stored references similarly seems
pointless, so I've just made `clear()` an empty method and marked it
deprecated.
This also fixes a minor potential issue with the destruction: it is
technically possible for `value` to be null (though this seems likely to
be rare in practice); this updates the check to look at `type` which
will always be non-null for a `Fetch`ed exception.
This also adds error_already_set round-trip throw tests to the test
suite.
2017-07-21 03:14:33 +00:00
|
|
|
|
2020-08-18 11:14:34 +00:00
|
|
|
// Test repr that cannot be displayed
|
2022-02-10 20:17:07 +00:00
|
|
|
m.def("simple_bool_passthrough", [](bool x) { return x; });
|
2020-08-18 11:14:34 +00:00
|
|
|
|
2021-05-27 15:00:18 +00:00
|
|
|
m.def("throw_should_be_translated_to_key_error", []() { throw shared_exception(); });
|
2021-08-24 00:30:01 +00:00
|
|
|
|
|
|
|
m.def("raise_from", []() {
|
|
|
|
PyErr_SetString(PyExc_ValueError, "inner");
|
|
|
|
py::raise_from(PyExc_ValueError, "outer");
|
|
|
|
throw py::error_already_set();
|
|
|
|
});
|
|
|
|
|
|
|
|
m.def("raise_from_already_set", []() {
|
|
|
|
try {
|
|
|
|
PyErr_SetString(PyExc_ValueError, "inner");
|
|
|
|
throw py::error_already_set();
|
2022-02-10 20:17:07 +00:00
|
|
|
} catch (py::error_already_set &e) {
|
2021-08-24 00:30:01 +00:00
|
|
|
py::raise_from(e, PyExc_ValueError, "outer");
|
|
|
|
throw py::error_already_set();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-01-14 19:22:47 +00:00
|
|
|
m.def("throw_nested_exception", []() {
|
|
|
|
try {
|
|
|
|
throw std::runtime_error("Inner Exception");
|
|
|
|
} catch (const std::runtime_error &) {
|
|
|
|
std::throw_with_nested(std::runtime_error("Outer Exception"));
|
|
|
|
}
|
|
|
|
});
|
2022-05-26 04:44:55 +00:00
|
|
|
|
|
|
|
m.def("error_already_set_what", [](const py::object &exc_type, const py::object &exc_value) {
|
|
|
|
PyErr_SetObject(exc_type.ptr(), exc_value.ptr());
|
|
|
|
std::string what = py::error_already_set().what();
|
|
|
|
bool py_err_set_after_what = (PyErr_Occurred() != nullptr);
|
|
|
|
PyErr_Clear();
|
|
|
|
return py::make_tuple(std::move(what), py_err_set_after_what);
|
|
|
|
});
|
2022-05-31 18:51:13 +00:00
|
|
|
|
|
|
|
m.def("test_cross_module_interleaved_error_already_set", []() {
|
|
|
|
auto cm = py::module_::import("cross_module_interleaved_error_already_set");
|
|
|
|
auto interleaved_error_already_set
|
|
|
|
= reinterpret_cast<void (*)()>(PyLong_AsVoidPtr(cm.attr("funcaddr").ptr()));
|
|
|
|
interleaved_error_already_set();
|
|
|
|
});
|
error_already_set::what() is now constructed lazily (#1895)
* error_already_set::what() is now constructed lazily
Prior to this commit throwing error_already_set was expensive due to the
eager construction of the error string (which required traversing the
Python stack). See #1853 for more context and an alternative take on the
issue.
Note that error_already_set no longer inherits from std::runtime_error
because the latter has no default constructor.
* Do not attempt to normalize if no exception occurred
This is not supported on PyPy-2.7 5.8.0.
* Extract exception name via tp_name
This is faster than dynamically looking up __name__ via GetAttrString.
Note though that the runtime of the code throwing an error_already_set
will be dominated by stack unwinding so the improvement will not be
noticeable.
Before:
396 ns ± 0.913 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
After:
277 ns ± 0.549 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Benchmark:
const std::string foo() {
PyErr_SetString(PyExc_KeyError, "");
const std::string &s = py::detail::error_string();
PyErr_Clear();
return s;
}
PYBIND11_MODULE(foo, m) {
m.def("foo", &::foo);
}
* Reverted error_already_set to subclass std::runtime_error
* Revert "Extract exception name via tp_name"
The implementation of __name__ is slightly more complex than that.
It handles the module name prefix, and heap-allocated types. We could
port it to pybind11 later on but for now it seems like an overkill.
This reverts commit f1435c7e6b068a1ed13ebd3db597ea3bb15aa398.
* Cosmit following @YannickJadoul's comments
Note that detail::error_string() no longer calls PyException_SetTraceback
as it is unncessary for pretty-printing the exception.
* Fixed PyPy build
* Moved normalization to error_already_set ctor
* Fix merge bugs
* Fix more merge errors
* Improve formatting
* Improve error message in rare case
* Revert back if statements
* Fix clang-tidy
* Try removing mutable
* Does build_mode release fix it
* Set to Debug to expose segfault
* Fix remove set error string
* Do not run error_string() more than once
* Trying setting the tracebackk to the value
* guard if m_type is null
* Try to debug PGI
* One last try for PGI
* Does reverting this fix PyPy
* Reviewer suggestions
* Remove unnecessary initialization
* Add noexcept move and explicit fail throw
* Optimize error_string creation
* Fix typo
* Revert noexcept
* Fix merge conflict error
* Abuse assignment operator
* Revert operator abuse
* See if we still need debug
* Remove unnecessary mutable
* Report "FATAL failure building pybind11::error_already_set error_string" and terminate process.
* Try specifying noexcept again
* Try explicit ctor
* default ctor is noexcept too
* Apply reviewer suggestions, simplify code, and make helper method private
* Remove unnecessary include
* Clang-Tidy fix
* detail::obj_class_name(), fprintf with [STDERR], [STDOUT] tags, polish comments
* consistently check m_lazy_what.empty() also in production builds
* Make a comment slightly less ambiguous.
* Bug fix: Remove `what();` from `restore()`.
It sure would need to be guarded by `if (m_type)`, otherwise `what()` fails and masks that no error was set (see update unit test). But since `error_already_set` is copyable, there is no point in releasing m_type, m_value, m_trace, therefore we can just as well avoid the runtime overhead of force-building `m_lazy_what`, it may never be used.
* Replace extremely opaque (unhelpful) error message with a truthful reflection of what we know.
* Fix clang-tidy error [performance-move-constructor-init].
* Make expected error message less specific.
* Various changes.
* bug fix: error_string(PyObject **, ...)
* Putting back the two eager PyErr_NormalizeException() calls.
* Change error_already_set() to call pybind11_fail() if the Python error indicator not set. The net result is that a std::runtime_error is thrown instead of error_already_set, but all tests pass as is.
* Remove mutable (fixes oversight in the previous commit).
* Normalize the exception only locally in error_string(). Python 3.6 & 3.7 test failures expected. This is meant for benchmarking, to determine if it is worth the trouble looking into the failures.
* clang-tidy: use auto
* Use `gil_scoped_acquire_local` in `error_already_set` destructor. See long comment.
* For Python < 3.8: `PyErr_NormalizeException` before `PyErr_WriteUnraisable`
* Go back to replacing the held Python exception with then normalized exception, if & when needed. Consistently document the side-effect.
* Slightly rewording comment. (There were also other failures.)
* Add 1-line comment for obj_class_name()
* Benchmark code, with results in this commit message.
function #calls test time [s] μs / call
master pure_unwind 729540 1.061 14.539876
err_set_unwind_err_clear 681476 1.040 15.260282
err_set_error_already_set 508038 1.049 20.640525
error_already_set_restore 555578 1.052 18.933288
pr1895_original_foo 244113 1.050 43.018168
PR / master
PR #1895 pure_unwind 736981 1.054 14.295685 98.32%
err_set_unwind_err_clear 685820 1.045 15.237399 99.85%
err_set_error_already_set 661374 1.046 15.811879 76.61%
error_already_set_restore 669881 1.048 15.645176 82.63%
pr1895_original_foo 318243 1.059 33.290806 77.39%
master @ commit ad146b2a1877e8ba3803f94a7837969835a297a7
Running tests in directory "/usr/local/google/home/rwgk/forked/pybind11/tests":
============================= test session starts ==============================
platform linux -- Python 3.9.10, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /usr/local/google/home/rwgk/forked/pybind11/tests, configfile: pytest.ini
collecting ... collected 5 items
test_perf_error_already_set.py::test_perf[pure_unwind]
PERF pure_unwind,729540,1.061,14.539876
PASSED
test_perf_error_already_set.py::test_perf[err_set_unwind_err_clear]
PERF err_set_unwind_err_clear,681476,1.040,15.260282
PASSED
test_perf_error_already_set.py::test_perf[err_set_error_already_set]
PERF err_set_error_already_set,508038,1.049,20.640525
PASSED
test_perf_error_already_set.py::test_perf[error_already_set_restore]
PERF error_already_set_restore,555578,1.052,18.933288
PASSED
test_perf_error_already_set.py::test_perf[pr1895_original_foo]
PERF pr1895_original_foo,244113,1.050,43.018168
PASSED
============================== 5 passed in 12.38s ==============================
pr1895 @ commit 8dff51d12e4af11aff415ee966070368fe606664
Running tests in directory "/usr/local/google/home/rwgk/forked/pybind11/tests":
============================= test session starts ==============================
platform linux -- Python 3.9.10, pytest-6.2.3, py-1.10.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: /usr/local/google/home/rwgk/forked/pybind11/tests, configfile: pytest.ini
collecting ... collected 5 items
test_perf_error_already_set.py::test_perf[pure_unwind]
PERF pure_unwind,736981,1.054,14.295685
PASSED
test_perf_error_already_set.py::test_perf[err_set_unwind_err_clear]
PERF err_set_unwind_err_clear,685820,1.045,15.237399
PASSED
test_perf_error_already_set.py::test_perf[err_set_error_already_set]
PERF err_set_error_already_set,661374,1.046,15.811879
PASSED
test_perf_error_already_set.py::test_perf[error_already_set_restore]
PERF error_already_set_restore,669881,1.048,15.645176
PASSED
test_perf_error_already_set.py::test_perf[pr1895_original_foo]
PERF pr1895_original_foo,318243,1.059,33.290806
PASSED
============================== 5 passed in 12.40s ==============================
clang++ -o pybind11/tests/test_perf_error_already_set.os -c -std=c++17 -fPIC -fvisibility=hidden -Os -flto -Wall -Wextra -Wconversion -Wcast-qual -Wdeprecated -Wnon-virtual-dtor -Wunused-result -isystem /usr/include/python3.9 -isystem /usr/include/eigen3 -DPYBIND11_STRICT_ASSERTS_CLASS_HOLDER_VS_TYPE_CASTER_MIX -DPYBIND11_TEST_BOOST -Ipybind11/include -I/usr/local/google/home/rwgk/forked/pybind11/include -I/usr/local/google/home/rwgk/clone/pybind11/include /usr/local/google/home/rwgk/forked/pybind11/tests/test_perf_error_already_set.cpp
clang++ -o lib/pybind11_tests.so -shared -fPIC -Os -flto -shared ...
Debian clang version 13.0.1-3+build2
Target: x86_64-pc-linux-gnu
Thread model: posix
* Changing call_repetitions_target_elapsed_secs to 0.1 for regular unit testing.
* Adding in `recursion_depth`
* Optimized ctor
* Fix silly bug in recurse_first_then_call()
* Add tests that have equivalent PyErr_Fetch(), PyErr_Restore() but no try-catch.
* Add call_error_string to tests. Sample only recursion_depth 0, 100.
* Show lazy-what speed-up in percent.
* Include real_work in benchmarks.
* Replace all PyErr_SetString() with generate_python_exception_with_traceback()
* Better organization of test loops.
* Add test_error_already_set_copy_move
* Fix bug in newly added test (discovered by clang-tidy): actually use move ctor
* MSVC detects the unreachable return
* change test_perf_error_already_set.py back to quick mode
* Inherit from std::exception (instead of std::runtime_error, which does not make sense anymore with the lazy what)
* Special handling under Windows.
* print with leading newline
* Removing test_perf_error_already_set (copies are under https://github.com/rwgk/rwgk_tbx/commit/7765113fbb659e1ea004c5ba24fb578244bc6cfd).
* Avoid gil and scope overhead if there is nothing to release.
* Restore default move ctor. "member function" instead of "function" (note that "method" is Python terminology).
* Delete error_already_set copy ctor.
* Make restore() non-const again to resolve clang-tidy failure (still experimenting).
* Bring back error_already_set copy ctor, to see if that resolves the 4 MSVC test failures.
* Add noexcept to error_already_set copy & move ctors (as suggested by @skylion007 IIUC).
* Trying one-by-one noexcept copy ctor for old compilers.
* Add back test covering copy ctor. Add another simple test that exercises the copy ctor.
* Exclude more older compilers from using the noexcept = default ctors. (The tests in the previous commit exposed that those are broken.)
* Factor out & reuse gil_scoped_acquire_local as gil_scoped_acquire_simple
* Guard gil_scoped_acquire_simple by _Py_IsFinalizing() check.
* what() GIL safety
* clang-tidy & Python 3.6 fixes
* Use `gil_scoped_acquire` in dtor, copy ctor, `what()`. Remove `_Py_IsFinalizing()` checks (they are racy: https://github.com/python/cpython/pull/28525).
* Remove error_scope from copy ctor.
* Add `error_scope` to `get_internals()`, to cover the situation that `get_internals()` is called from the `error_already_set` dtor while a new Python error is in flight already. Also backing out `gil_scoped_acquire_simple` change.
* Add `FlakyException` tests with failure triggers in `__init__` and `__str__`
THIS IS STILL A WORK IN PROGRESS. This commit is only an important resting point.
This commit is a first attempt at addressing the observation that `PyErr_NormalizeException()` completely replaces the original exception if `__init__` fails. This can be very confusing even in small applications, and extremely confusing in large ones.
* Tweaks to resolve Py 3.6 and PyPy CI failures.
* Normalize Python exception immediately in error_already_set ctor.
For background see: https://github.com/pybind/pybind11/pull/1895#issuecomment-1135304081
* Fix oversights based on CI failures (copy & move ctor initialization).
* Move @pytest.mark.xfail("env.PYPY") after @pytest.mark.parametrize(...)
* Use @pytest.mark.skipif (xfail does not work for segfaults, of course).
* Remove unused obj_class_name_or() function (it was added only under this PR).
* Remove already obsolete C++ comments and code that were added only under this PR.
* Slightly better (newly added) comments.
* Factor out detail::error_fetch_and_normalize. Preparation for producing identical results from error_already_set::what() and detail::error_string(). Note that this is a very conservative refactoring. It would be much better to first move detail::error_string into detail/error_string.h
* Copy most of error_string() code to new error_fetch_and_normalize::complete_lazy_error_string()
* Remove all error_string() code from detail/type_caster_base.h. Note that this commit includes a subtle bug fix: previously error_string() restored the Python error, which will upset pybind11_fail(). This never was a problem in practice because the two PyType_Ready() calls in detail/class.h do not usually fail.
* Return const std::string& instead of const char * and move error_string() to pytypes.h
* Remove gil_scope_acquire from error_fetch_and_normalize, add back to error_already_set
* Better handling of FlakyException __str__ failure.
* Move error_fetch_and_normalize::complete_lazy_error_string() implementation from pybind11.h to pytypes.h
* Add error_fetch_and_normalize::release_py_object_references() and use from error_already_set dtor.
* Use shared_ptr for m_fetched_error => 1. non-racy, copy ctor that does not need the GIL; 2. enables guard against duplicate restore() calls.
* Add comments.
* Trivial renaming of a newly introduced member function.
* Workaround for PyPy
* Bug fix (oversight). Only valgrind got this one.
* Use shared_ptr custom deleter for m_fetched_error in error_already_set. This enables removing the dtor, copy ctor, move ctor completely.
* Further small simplification. With the GIL held, simply deleting the raw_ptr takes care of everything.
* IWYU cleanup
```
iwyu version: include-what-you-use 0.17 based on Debian clang version 13.0.1-3+build2
```
Command used:
```
iwyu -c -std=c++17 -DPYBIND11_TEST_BOOST -Iinclude/pybind11 -I/usr/include/python3.9 -I/usr/include/eigen3 include/pybind11/pytypes.cpp
```
pytypes.cpp is a temporary file: `#include "pytypes.h"`
The raw output is very long and noisy.
I decided to use `#include <cstddef>` instead of `#include <cstdio>` for `std::size_t` (iwyu sticks to the manual choice).
I ignored all iwyu suggestions that are indirectly covered by `#include <Python.h>`.
I manually verified that all added includes are actually needed.
Co-authored-by: Aaron Gokaslan <skylion.aaron@gmail.com>
Co-authored-by: Ralf W. Grosse-Kunstleve <rwgk@google.com>
2022-06-02 23:17:38 +00:00
|
|
|
|
|
|
|
m.def("test_error_already_set_double_restore", [](bool dry_run) {
|
|
|
|
PyErr_SetString(PyExc_ValueError, "Random error.");
|
|
|
|
py::error_already_set e;
|
|
|
|
e.restore();
|
|
|
|
PyErr_Clear();
|
|
|
|
if (!dry_run) {
|
|
|
|
e.restore();
|
|
|
|
}
|
|
|
|
});
|
2017-07-23 16:26:17 +00:00
|
|
|
}
|