1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
diff --git a/python/pybind11_v3/pybind11/functional.h b/python/pybind11_v3/pybind11/functional.h
index 8f59f5fe5..5a554abad 100644
--- a/python/pybind11_v3/pybind11/functional.h
+++ b/python/pybind11_v3/pybind11/functional.h
@@ -29,12 +29,18 @@ struct func_handle {
}
func_handle(const func_handle &f_) { operator=(f_); }
func_handle &operator=(const func_handle &f_) {
- gil_scoped_acquire acq;
+ // Called on the way into a Regina function that expects
+ // a std::function argument. In such situations the
+ // interpreter should already be holding the GIL.
+ // gil_scoped_acquire acq;
f = f_.f;
return *this;
}
~func_handle() {
- gil_scoped_acquire acq;
+ // Called on the way into and also out of a Regina function
+ // that expects a std::function argument. In such situations
+ // the interpreter should already be holding the GIL.
+ // gil_scoped_acquire acq;
function kill_f(std::move(f));
}
};
@@ -49,7 +55,10 @@ template <typename Return, typename... Args>
struct func_wrapper : func_wrapper_base {
using func_wrapper_base::func_wrapper_base;
Return operator()(Args... args) const { // NOLINT(performance-unnecessary-value-param)
- gil_scoped_acquire acq;
+ // Called when a std::function is executed as a callback within
+ // one of Regina's own functions. In such situations Regina's
+ // Python bindings are responsible for ensuring the GIL is held.
+ // gil_scoped_acquire acq;
// casts the returned object as a rvalue to the return type
return hfunc.f(std::forward<Args>(args)...).template cast<Return>();
}
diff --git a/python/pybind11_v3/pybind11/gil.h b/python/pybind11_v3/pybind11/gil.h
index 4222a035f..fef69b641 100644
--- a/python/pybind11_v3/pybind11/gil.h
+++ b/python/pybind11_v3/pybind11/gil.h
@@ -197,3 +197,30 @@ private:
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
#endif // !PYBIND11_SIMPLE_GIL_MANAGEMENT
+
+PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
+
+/**
+ * A less lightweight version of gil_scoped_acquire that actually checks
+ * whether we are already holding the GIL before trying to acquire it.
+ *
+ * This is needed (for example) in PacketListener callbacks in the GUI, since
+ * we do not know whether listener methods are reacting to some action in the
+ * C++ GUI (where the GIL will not be held) or in a Python console (where the
+ * GIL will be held).
+ *
+ * - Ben Burton, 25/06/2025.
+ */
+class safe_gil_scoped_acquire {
+ std::optional<gil_scoped_acquire> gil;
+public:
+ safe_gil_scoped_acquire() {
+ if (! PyGILState_Check())
+ gil.emplace();
+ }
+ safe_gil_scoped_acquire(const safe_gil_scoped_acquire&) = delete;
+ safe_gil_scoped_acquire& operator = (const safe_gil_scoped_acquire&) =
+ delete;
+};
+
+PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
diff --git a/python/pybind11_v3/pybind11/pybind11.h b/python/pybind11_v3/pybind11/pybind11.h
index 06be7f1d4..d20de7ecc 100644
--- a/python/pybind11_v3/pybind11/pybind11.h
+++ b/python/pybind11_v3/pybind11/pybind11.h
@@ -33,6 +33,10 @@
#include <utility>
#include <vector>
+// This allows us to verify that we are #including our own patched pybind11,
+// not some other pybind11 that has been installed system-wide.
+#define REGINA_PYBIND11 1
+
// See PR #5448. This warning suppression is needed for the PYBIND11_OVERRIDE macro family.
// NOTE that this is NOT embedded in a push/pop pair because that is very difficult to achieve.
#if defined(__clang_major__) && __clang_major__ < 14
@@ -43,6 +47,10 @@ PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")
# include <cxxabi.h>
#endif
+namespace regina {
+ const char* pythonTypename(const std::type_info*);
+}
+
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
/* https://stackoverflow.com/questions/46798456/handling-gccs-noexcept-type-warning
@@ -101,6 +109,16 @@ inline std::string replace_newlines_and_squash(const char *text) {
return result.substr(str_begin, str_range);
}
+/* Regina-specific tweak to generate_function_signature */
+inline std::string fix_regina(const std::string& modulename, const std::string& qualname) {
+ if (modulename == "regina.engine")
+ return "regina." + qualname;
+ else if (modulename == "regina.engine.internal")
+ return "regina.internal." + qualname;
+ else
+ return modulename + "." + qualname;
+}
+
/* Generate a proper function signature */
inline std::string generate_function_signature(const char *type_caster_name_field,
detail::function_record *func_rec,
@@ -157,18 +175,43 @@ inline std::string generate_function_signature(const char *type_caster_name_fiel
if (!t) {
pybind11_fail("Internal error while parsing type signature (1)");
}
- if (auto *tinfo = detail::get_type_info(*t)) {
+ if (const char* name = regina::pythonTypename(t)) {
+ signature += name;
+ } else if (auto *tinfo = detail::get_type_info(*t)) {
handle th((PyObject *) tinfo->type);
- signature += th.attr("__module__").cast<std::string>() + "."
- + th.attr("__qualname__").cast<std::string>();
+ std::string qualname;
+ try {
+ qualname = th.attr("__qualname__").cast<std::string>();
+ } catch (...) {
+ // In Python 3.12 we get an error in our stripped-down
+ // interpreter since PythonOutputStream is missing both
+ // __qualname__ and __name__. Do not make this fatal.
+ qualname = "<unknown>";
+ }
+ const auto m = th.attr("__module__").cast<std::string>();
+ signature += fix_regina(m, qualname);
} else if (auto th = detail::global_internals_native_enum_type_map_get_item(*t)) {
- signature += th.attr("__module__").cast<std::string>() + "."
- + th.attr("__qualname__").cast<std::string>();
+ std::string qualname;
+ try {
+ qualname = th.attr("__qualname__").cast<std::string>();
+ } catch (...) {
+ // See above for the explanation here.
+ qualname = "<unknown>";
+ }
+ const auto m = th.attr("__module__").cast<std::string>();
+ signature += fix_regina(m, qualname);
} else if (func_rec->is_new_style_constructor && arg_index == 0) {
// A new-style `__init__` takes `self` as `value_and_holder`.
// Rewrite it to the proper class type.
- signature += func_rec->scope.attr("__module__").cast<std::string>() + "."
- + func_rec->scope.attr("__qualname__").cast<std::string>();
+ std::string qualname;
+ try {
+ qualname = func_rec->scope.attr("__qualname__").cast<std::string>();
+ } catch (...) {
+ // See above for the explanation here.
+ qualname = "<unknown>";
+ }
+ const auto m = func_rec->scope.attr("__module__").cast<std::string>();
+ signature += fix_regina(m, qualname);
} else {
signature += detail::quote_cpp_type_name(detail::clean_type_id(t->name()));
}
@@ -1110,6 +1153,16 @@ protected:
} catch (abi::__forced_unwind &) {
throw;
#endif
+ } catch (const pybind11::stop_iteration& stop) {
+ /* We prioritise catching stop_iteration before any of the other
+ exception logic below, since stop_iteration is arguably the one
+ setting where exceptions are normal as opposed to "exceptional".
+ The default exception logic involves many try/catch/rethrow
+ blocks, and in settings where try/catch is slow (e.g., running
+ within SageMath on macOS), this can add to a noticeable
+ performance penalty when using iterators. */
+ PyErr_SetString(PyExc_StopIteration, stop.what());
+ return nullptr;
} catch (...) {
try_translate_exceptions();
return nullptr;
@@ -3213,10 +3266,10 @@ template <typename type>
class exception : public object {
public:
exception() = default;
- exception(handle scope, const char *name, handle base = PyExc_Exception) {
+ exception(handle scope, const char *name, const char* doc, handle base) {
std::string full_name
= scope.attr("__name__").cast<std::string>() + std::string(".") + name;
- m_ptr = PyErr_NewException(const_cast<char *>(full_name.c_str()), base.ptr(), nullptr);
+ m_ptr = PyErr_NewExceptionWithDoc(const_cast<char *>(full_name.c_str()), doc, base.ptr(), nullptr);
if (hasattr(scope, "__dict__") && scope.attr("__dict__").contains(name)) {
pybind11_fail("Error during initialization: multiple incompatible "
"definitions with name \""
@@ -3521,7 +3521,7 @@ function get_override(const T *this_ptr, const char *name) {
#define PYBIND11_OVERRIDE_IMPL(ret_type, cname, name, ...) \
do { \
- pybind11::gil_scoped_acquire gil; \
+ pybind11::safe_gil_scoped_acquire gil; \
pybind11::function override \
= pybind11::get_override(static_cast<const cname *>(this), name); \
if (override) { \
diff --git a/python/pybind11_v3/pybind11/subinterpreter.h b/python/pybind11_v3/pybind11/subinterpreter.h
index 9f2f704c5..cb666cf28 100644
--- a/python/pybind11_v3/pybind11/subinterpreter.h
+++ b/python/pybind11_v3/pybind11/subinterpreter.h
@@ -21,7 +21,7 @@
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
PYBIND11_NAMESPACE_BEGIN(detail)
-PyInterpreterState *get_interpreter_state_unchecked() {
+inline PyInterpreterState *get_interpreter_state_unchecked() {
auto cur_tstate = get_thread_state_unchecked();
if (cur_tstate)
return cur_tstate->interp;
|