diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45f8675889..2be71f404f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,7 +84,7 @@ jobs: python-version: '3.12' cmake-args: -DPYBIND11_TEST_SMART_HOLDER=ON -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON - runs-on: ubuntu-latest - python-version: '3.13t' + python-version: '3.14t' cmake-args: -DCMAKE_CXX_STANDARD=20 -DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON - runs-on: ubuntu-latest python-version: '3.14' @@ -102,12 +102,12 @@ jobs: - runs-on: macos-15-intel python-version: '3.11' cmake-args: -DPYBIND11_TEST_SMART_HOLDER=ON + - runs-on: macos-15-intel + python-version: '3.13' + cmake-args: -DCMAKE_CXX_STANDARD=11 - runs-on: macos-latest python-version: '3.12' cmake-args: -DCMAKE_CXX_STANDARD=17 -DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON - - runs-on: macos-15-intel - python-version: '3.13t' - cmake-args: -DCMAKE_CXX_STANDARD=11 - runs-on: macos-latest python-version: '3.14t' cmake-args: -DCMAKE_CXX_STANDARD=20 @@ -138,9 +138,6 @@ jobs: - runs-on: windows-2022 python-version: '3.13' cmake-args: -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL - - runs-on: windows-latest - python-version: '3.13t' - cmake-args: -DCMAKE_CXX_STANDARD=17 - runs-on: windows-latest python-version: '3.14' cmake-args: -DCMAKE_CXX_STANDARD=20 @@ -240,7 +237,7 @@ jobs: manylinux: - name: Manylinux on 🐍 3.13t • GIL + name: Manylinux on 🐍 3.14t if: github.event.pull_request.draft == false runs-on: ubuntu-latest timeout-minutes: 40 @@ -257,7 +254,7 @@ jobs: run: uv tool install ninja - name: Configure via preset - run: cmake --preset venv -DPYBIND11_CREATE_WITH_UV=python3.13t + run: cmake --preset venv -DPYBIND11_CREATE_WITH_UV=python3.14t - name: Build C++11 run: cmake --build --preset venv diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 9b3e69f4db..37cea1a311 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -230,6 +230,7 @@ using instance_map = std::unordered_multimap; #ifdef Py_GIL_DISABLED // Wrapper around PyMutex to provide BasicLockable semantics class pymutex { + friend class pycritical_section; PyMutex mutex; public: @@ -238,6 +239,23 @@ class pymutex { void unlock() { PyMutex_Unlock(&mutex); } }; +class pycritical_section { + pymutex &mutex; + PyCriticalSection cs; + +public: + explicit pycritical_section(pymutex &m) : mutex(m) { + PyCriticalSection_BeginMutex(&cs, &mutex.mutex); + } + ~pycritical_section() { PyCriticalSection_End(&cs); } + + // Non-copyable and non-movable to prevent double-unlock + pycritical_section(const pycritical_section &) = delete; + pycritical_section &operator=(const pycritical_section &) = delete; + pycritical_section(pycritical_section &&) = delete; + pycritical_section &operator=(pycritical_section &&) = delete; +}; + // Instance map shards are used to reduce mutex contention in free-threaded Python. struct instance_map_shard { instance_map registered_instances; @@ -856,7 +874,7 @@ inline local_internals &get_local_internals() { } #ifdef Py_GIL_DISABLED -# define PYBIND11_LOCK_INTERNALS(internals) std::unique_lock lock((internals).mutex) +# define PYBIND11_LOCK_INTERNALS(internals) pycritical_section lock((internals).mutex) #else # define PYBIND11_LOCK_INTERNALS(internals) #endif @@ -885,7 +903,7 @@ inline auto with_exception_translators(const F &cb) get_local_internals().registered_exception_translators)) { auto &internals = get_internals(); #ifdef Py_GIL_DISABLED - std::unique_lock lock((internals).exception_translator_mutex); + pycritical_section lock((internals).exception_translator_mutex); #endif auto &local_internals = get_local_internals(); return cb(internals.registered_exception_translators, diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 02d2e72c2c..f88fc20272 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -3173,6 +3173,7 @@ iterator make_iterator_impl(Iterator first, Sentinel last, Extra &&...extra) { using state = detail::iterator_state; // TODO: state captures only the types of Extra, not the values + PYBIND11_LOCK_INTERNALS(get_internals()); if (!detail::get_type_info(typeid(state), false)) { class_(handle(), "iterator", pybind11::module_local()) .def(