diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index ad2ff74cb..aa03097f7 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -90,14 +90,21 @@ public: } ~thread_specific_storage() { - // This destructor could be called *after* Py_Finalize(). That *SHOULD BE* fine. The - // following details what happens when PyThread_tss_free is called. - // PYBIND11_TLS_FREE is PyThread_tss_free on python 3.7+. On older python, it does + // This destructor is often called *after* Py_Finalize(). That *SHOULD BE* fine on most + // platforms. The following details what happens when PyThread_tss_free is called in + // CPython. PYBIND11_TLS_FREE is PyThread_tss_free on python 3.7+. On older python, it does // nothing. PyThread_tss_free calls PyThread_tss_delete and PyMem_RawFree. // PyThread_tss_delete just calls TlsFree (on Windows) or pthread_key_delete (on *NIX). // Neither of those have anything to do with CPython internals. PyMem_RawFree *requires* // that the `key` be allocated with the CPython allocator (as it is by // PyThread_tss_create). + // However, in GraalPy (as of v24.2 or older), TSS is implemented by Java and this call + // requires a living Python interpreter. +#ifdef GRAALVM_PYTHON + if (!Py_IsInitialized() || _Py_IsFinalizing()) { + return; + } +#endif PYBIND11_TLS_FREE(key_); } diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index f57a7cde0..62bf55591 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -3148,17 +3148,22 @@ typing::Iterator make_value_iterator(Type &value, Extra &&...extra) { template void implicitly_convertible() { + static int tss_sentinel_pointee = 1; // arbitrary value struct set_flag { - bool &flag; - explicit set_flag(bool &flag_) : flag(flag_) { flag_ = true; } - ~set_flag() { flag = false; } + thread_specific_storage &flag; + explicit set_flag(thread_specific_storage &flag_) : flag(flag_) { + flag = &tss_sentinel_pointee; // trick: the pointer itself is the sentinel + } + ~set_flag() { flag.reset(nullptr); } + + // Prevent copying/moving to ensure RAII guard is used safely + set_flag(const set_flag &) = delete; + set_flag(set_flag &&) = delete; + set_flag &operator=(const set_flag &) = delete; + set_flag &operator=(set_flag &&) = delete; }; auto implicit_caster = [](PyObject *obj, PyTypeObject *type) -> PyObject * { -#ifdef Py_GIL_DISABLED - thread_local bool currently_used = false; -#else - static bool currently_used = false; -#endif + static thread_specific_storage currently_used; if (currently_used) { // implicit conversions are non-reentrant return nullptr; }