Skip to content

Commit 3ae5a17

Browse files
authored
Add fallback implementation of PyCriticalSection_BeginMutex for Python 3.13t (#5981)
* Add failback implementation of `PyCriticalSection_BeginMutex` for Python 3.13t * Add comment for Python version * Use `_PyCriticalSection_BeginSlow` * Add forward declaration * Fix forward declaration * Remove always true condition `defined(PY_VERSION_HEX)` * Detect musllinux * Add manylinux test * Use direct mutex locking for Python 3.13t `_PyCriticalSection_BeginSlow` is a private CPython function not exported on Linux. For Python < 3.14.0rc1, use direct `mutex.lock()`/`mutex.unlock()` instead of critical section APIs. * Empty commit to trigger CI * Empty commit to trigger CI * Empty commit to trigger CI * Run apt update before apt install * Remove unnecessary prefix * Add manylinux test with Python 3.13t * Simplify pycritical_section with std::unique_lock fallback for Python < 3.14 * Fix potential deadlock in make_iterator_impl for Python 3.13t Refactor pycritical_section into a unified class with internal version checks instead of using a type alias fallback. Skip locking in make_iterator_impl for Python < 3.14.0rc1 to avoid deadlock during type registration, as pycritical_section cannot release the mutex during Python callbacks without PyCriticalSection_BeginMutex. * Add reference for xfail message
1 parent 5f2c678 commit 3ae5a17

6 files changed

Lines changed: 52 additions & 5 deletions

File tree

.github/workflows/ci.yml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,11 +237,23 @@ jobs:
237237

238238

239239
manylinux:
240-
name: Manylinux on 🐍 3.14t
240+
name: Manylinux on 🐍 ${{ matrix.python-version }} (${{ matrix.container }})
241241
if: github.event.pull_request.draft == false
242+
strategy:
243+
fail-fast: false
244+
matrix:
245+
include:
246+
- container: quay.io/pypa/manylinux_2_28_x86_64:latest
247+
python-version: '3.13t'
248+
- container: quay.io/pypa/musllinux_1_2_x86_64:latest
249+
python-version: '3.13t'
250+
- container: quay.io/pypa/manylinux_2_28_x86_64:latest
251+
python-version: '3.14t'
252+
- container: quay.io/pypa/musllinux_1_2_x86_64:latest
253+
python-version: '3.14t'
242254
runs-on: ubuntu-latest
243255
timeout-minutes: 40
244-
container: quay.io/pypa/musllinux_1_2_x86_64:latest
256+
container: ${{ matrix.container }}
245257
steps:
246258
- uses: actions/checkout@v6
247259
with:
@@ -254,7 +266,7 @@ jobs:
254266
run: uv tool install ninja
255267

256268
- name: Configure via preset
257-
run: cmake --preset venv -DPYBIND11_CREATE_WITH_UV=python3.14t
269+
run: cmake --preset venv -DPYBIND11_CREATE_WITH_UV="${{ matrix.python-version }}"
258270

259271
- name: Build C++11
260272
run: cmake --build --preset venv

.github/workflows/reusable-standard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
4545
- name: Setup Boost (Linux)
4646
if: runner.os == 'Linux'
47-
run: sudo apt-get install libboost-dev
47+
run: sudo apt-get update && sudo apt-get install -y libboost-dev
4848

4949
- name: Setup Boost (macOS)
5050
if: runner.os == 'macOS'

include/pybind11/detail/internals.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,13 +241,27 @@ class pymutex {
241241

242242
class pycritical_section {
243243
pymutex &mutex;
244+
# if PY_VERSION_HEX >= 0x030E00C1 // 3.14.0rc1
244245
PyCriticalSection cs;
246+
# endif
245247

246248
public:
247249
explicit pycritical_section(pymutex &m) : mutex(m) {
250+
// PyCriticalSection_BeginMutex was added in Python 3.15.0a1 and backported to 3.14.0rc1
251+
# if PY_VERSION_HEX >= 0x030E00C1 // 3.14.0rc1
248252
PyCriticalSection_BeginMutex(&cs, &mutex.mutex);
253+
# else
254+
// Fall back to direct mutex locking for older free-threaded Python versions
255+
mutex.lock();
256+
# endif
257+
}
258+
~pycritical_section() {
259+
# if PY_VERSION_HEX >= 0x030E00C1 // 3.14.0rc1
260+
PyCriticalSection_End(&cs);
261+
# else
262+
mutex.unlock();
263+
# endif
249264
}
250-
~pycritical_section() { PyCriticalSection_End(&cs); }
251265

252266
// Non-copyable and non-movable to prevent double-unlock
253267
pycritical_section(const pycritical_section &) = delete;

include/pybind11/pybind11.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3173,7 +3173,11 @@ iterator make_iterator_impl(Iterator first, Sentinel last, Extra &&...extra) {
31733173
using state = detail::iterator_state<Access, Policy, Iterator, Sentinel, ValueType, Extra...>;
31743174
// TODO: state captures only the types of Extra, not the values
31753175

3176+
// For Python < 3.14.0rc1, pycritical_section uses direct mutex locking (same as a unique
3177+
// lock), which may deadlock during type registration. See detail/internals.h for details.
3178+
#if PY_VERSION_HEX >= 0x030E00C1 // 3.14.0rc1
31763179
PYBIND11_LOCK_INTERNALS(get_internals());
3180+
#endif
31773181
if (!detail::get_type_info(typeid(state), false)) {
31783182
class_<state>(handle(), "iterator", pybind11::module_local())
31793183
.def(

tests/env.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@
1111
WIN = sys.platform.startswith("win32") or sys.platform.startswith("cygwin")
1212
FREEBSD = sys.platform.startswith("freebsd")
1313

14+
MUSLLINUX = False
15+
MANYLINUX = False
16+
if LINUX:
17+
18+
def _is_musl() -> bool:
19+
libc, _ = platform.libc_ver()
20+
return libc == "musl" or (libc != "glibc" and libc != "")
21+
22+
MUSLLINUX = _is_musl()
23+
MANYLINUX = not MUSLLINUX
24+
del _is_musl
25+
1426
CPYTHON = platform.python_implementation() == "CPython"
1527
PYPY = platform.python_implementation() == "PyPy"
1628
GRAALPY = sys.implementation.name == "graalpy"

tests/test_multiple_interpreters.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,11 @@ def test_import_in_subinterpreter_before_main():
408408
@pytest.mark.skipif(
409409
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
410410
)
411+
@pytest.mark.xfail(
412+
env.MUSLLINUX,
413+
reason="Flaky on musllinux, see also: https://github.com/pybind/pybind11/pull/5972#discussion_r2755283335",
414+
strict=False,
415+
)
411416
@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
412417
def test_import_in_subinterpreter_concurrently():
413418
"""Tests that importing a module in multiple subinterpreters concurrently works correctly"""

0 commit comments

Comments
 (0)