From d534bd670e7de67bbc93999b6d24861bc3afd7a7 Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Wed, 8 Feb 2017 20:23:56 +0100 Subject: [PATCH] Fix handling of Python exceptions during module initialization (#657) Fixes #656. Before this commit, the problematic sequence was: 1. `catch (const std::exception &e)` gets a Python exception, i.e. `error_already_set`. 2. `PyErr_SetString(PyExc_ImportError, e.what())` sets an `ImportError`. 3. `~error_already_set()` now runs, but `gil_scoped_acquire` fails due to an unhandled `ImportError` (which was just set in step 2). This commit adds a separate catch block for Python exceptions which just clears the Python error state a little earlier and replaces it with an `ImportError`, thus making sure that there is only a single Python exception in flight at a time. (After step 2 in the sequence above, there were effectively two Python expections set.) --- include/pybind11/common.h | 7 +++++++ include/pybind11/pybind11.h | 3 +-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/include/pybind11/common.h b/include/pybind11/common.h index bbe92dfde..7299fbf81 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -189,6 +189,10 @@ extern "C" { } \ try { \ return pybind11_init(); \ + } catch (pybind11::error_already_set &e) { \ + e.clear(); \ + PyErr_SetString(PyExc_ImportError, e.what()); \ + return nullptr; \ } catch (const std::exception &e) { \ PyErr_SetString(PyExc_ImportError, e.what()); \ return nullptr; \ @@ -561,6 +565,9 @@ public: /// Give the error back to Python void restore() { PyErr_Restore(type, value, trace); type = value = trace = nullptr; } + /// Clear the held Python error state (the C++ `what()` message remains intact) + void clear() { restore(); PyErr_Clear(); } + private: PyObject *type, *value, *trace; }; diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 05d6b068b..006a4ff6c 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1884,8 +1884,7 @@ class gil_scoped_release { }; error_already_set::~error_already_set() { if (value) { gil_scoped_acquire gil; - PyErr_Restore(type, value, trace); - PyErr_Clear(); + clear(); } }