mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-21 20:55:11 +00:00
fix: Python 3.13t with GIL (#5139)
* ci: try Python 3.13t Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * fix: support Python 3.13t Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * fix: patch PyPy Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * tests: one more int cast Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * tests: cleanup Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * refactor: use named constant in tests for immortal refcounts Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * docs: move comment about free threaded Python Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> --------- Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
This commit is contained in:
parent
a5b9e50f68
commit
ae6432b817
29
.github/workflows/ci.yml
vendored
29
.github/workflows/ci.yml
vendored
@ -196,6 +196,35 @@ jobs:
|
|||||||
pytest tests/extra_setuptools
|
pytest tests/extra_setuptools
|
||||||
if: "!(matrix.runs-on == 'windows-2022')"
|
if: "!(matrix.runs-on == 'windows-2022')"
|
||||||
|
|
||||||
|
manylinux:
|
||||||
|
name: Manylinux on 🐍 3.13t • GIL
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 40
|
||||||
|
container: quay.io/pypa/musllinux_1_2_x86_64:latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Prepare venv
|
||||||
|
run: python3.13 -m venv .venv
|
||||||
|
|
||||||
|
- name: Install Python deps
|
||||||
|
run: .venv/bin/pip install -r tests/requirements.txt
|
||||||
|
|
||||||
|
- name: Configure C++11
|
||||||
|
run: >
|
||||||
|
cmake -S. -Bbuild
|
||||||
|
-DPYBIND11_WERROR=ON
|
||||||
|
-DDOWNLOAD_CATCH=ON
|
||||||
|
-DDOWNLOAD_EIGEN=ON
|
||||||
|
-DPython_ROOT_DIR=.venv
|
||||||
|
|
||||||
|
- name: Build C++11
|
||||||
|
run: cmake --build build -j2
|
||||||
|
|
||||||
|
- name: Python tests C++11
|
||||||
|
run: cmake --build build --target pytest -j2
|
||||||
|
|
||||||
deadsnakes:
|
deadsnakes:
|
||||||
strategy:
|
strategy:
|
||||||
|
@ -183,7 +183,15 @@ public:
|
|||||||
str_attr_accessor doc() const;
|
str_attr_accessor doc() const;
|
||||||
|
|
||||||
/// Return the object's current reference count
|
/// Return the object's current reference count
|
||||||
int ref_count() const { return static_cast<int>(Py_REFCNT(derived().ptr())); }
|
ssize_t ref_count() const {
|
||||||
|
#ifdef PYPY_VERSION
|
||||||
|
// PyPy uses the top few bits for REFCNT_FROM_PYPY & REFCNT_FROM_PYPY_LIGHT
|
||||||
|
// Following pybind11 2.12.1 and older behavior and removing this part
|
||||||
|
return static_cast<ssize_t>(static_cast<int>(Py_REFCNT(derived().ptr())));
|
||||||
|
#else
|
||||||
|
return Py_REFCNT(derived().ptr());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// TODO PYBIND11_DEPRECATED(
|
// TODO PYBIND11_DEPRECATED(
|
||||||
// "Call py::type::handle_of(h) or py::type::of(h) instead of h.get_type()")
|
// "Call py::type::handle_of(h) or py::type::of(h) instead of h.get_type()")
|
||||||
|
@ -89,6 +89,8 @@ PYBIND11_MODULE(pybind11_tests, m) {
|
|||||||
#endif
|
#endif
|
||||||
m.attr("cpp_std") = cpp_std();
|
m.attr("cpp_std") = cpp_std();
|
||||||
m.attr("PYBIND11_INTERNALS_ID") = PYBIND11_INTERNALS_ID;
|
m.attr("PYBIND11_INTERNALS_ID") = PYBIND11_INTERNALS_ID;
|
||||||
|
// Free threaded Python uses UINT32_MAX for immortal objects.
|
||||||
|
m.attr("PYBIND11_REFCNT_IMMORTAL") = UINT32_MAX;
|
||||||
m.attr("PYBIND11_SIMPLE_GIL_MANAGEMENT") =
|
m.attr("PYBIND11_SIMPLE_GIL_MANAGEMENT") =
|
||||||
#if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
|
#if defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
|
||||||
true;
|
true;
|
||||||
|
@ -3,7 +3,7 @@ from unittest import mock
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import env
|
import env
|
||||||
from pybind11_tests import ConstructorStats, UserType
|
from pybind11_tests import PYBIND11_REFCNT_IMMORTAL, ConstructorStats, UserType
|
||||||
from pybind11_tests import class_ as m
|
from pybind11_tests import class_ as m
|
||||||
|
|
||||||
|
|
||||||
@ -377,7 +377,9 @@ def test_class_refcount():
|
|||||||
refcount_3 = getrefcount(cls)
|
refcount_3 = getrefcount(cls)
|
||||||
|
|
||||||
assert refcount_1 == refcount_3
|
assert refcount_1 == refcount_3
|
||||||
assert refcount_2 > refcount_1
|
assert (refcount_2 > refcount_1) or (
|
||||||
|
refcount_2 == refcount_1 == PYBIND11_REFCNT_IMMORTAL
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_reentrant_implicit_conversion_failure(msg):
|
def test_reentrant_implicit_conversion_failure(msg):
|
||||||
|
@ -7,6 +7,13 @@ if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STR
|
|||||||
return()
|
return()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(TARGET Python::Module AND NOT TARGET Python::Python)
|
||||||
|
message(STATUS "Skipping embed test since no embed libs found")
|
||||||
|
add_custom_target(cpptest) # Dummy target since embedding is not supported.
|
||||||
|
set(_suppress_unused_variable_warning "${DOWNLOAD_CATCH}")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(Catch 2.13.9)
|
find_package(Catch 2.13.9)
|
||||||
|
|
||||||
if(CATCH_FOUND)
|
if(CATCH_FOUND)
|
||||||
|
@ -150,10 +150,13 @@ TEST_SUBMODULE(kwargs_and_defaults, m) {
|
|||||||
|
|
||||||
// test_args_refcount
|
// test_args_refcount
|
||||||
// PyPy needs a garbage collection to get the reference count values to match CPython's behaviour
|
// PyPy needs a garbage collection to get the reference count values to match CPython's behaviour
|
||||||
|
// PyPy uses the top few bits for REFCNT_FROM_PYPY & REFCNT_FROM_PYPY_LIGHT, so truncate
|
||||||
#ifdef PYPY_VERSION
|
#ifdef PYPY_VERSION
|
||||||
# define GC_IF_NEEDED ConstructorStats::gc()
|
# define GC_IF_NEEDED ConstructorStats::gc()
|
||||||
|
# define REFCNT(x) (int) Py_REFCNT(x)
|
||||||
#else
|
#else
|
||||||
# define GC_IF_NEEDED
|
# define GC_IF_NEEDED
|
||||||
|
# define REFCNT(x) Py_REFCNT(x)
|
||||||
#endif
|
#endif
|
||||||
m.def("arg_refcount_h", [](py::handle h) {
|
m.def("arg_refcount_h", [](py::handle h) {
|
||||||
GC_IF_NEEDED;
|
GC_IF_NEEDED;
|
||||||
@ -172,7 +175,7 @@ TEST_SUBMODULE(kwargs_and_defaults, m) {
|
|||||||
py::tuple t(a.size());
|
py::tuple t(a.size());
|
||||||
for (size_t i = 0; i < a.size(); i++) {
|
for (size_t i = 0; i < a.size(); i++) {
|
||||||
// Use raw Python API here to avoid an extra, intermediate incref on the tuple item:
|
// Use raw Python API here to avoid an extra, intermediate incref on the tuple item:
|
||||||
t[i] = (int) Py_REFCNT(PyTuple_GET_ITEM(a.ptr(), static_cast<py::ssize_t>(i)));
|
t[i] = REFCNT(PyTuple_GET_ITEM(a.ptr(), static_cast<py::ssize_t>(i)));
|
||||||
}
|
}
|
||||||
return t;
|
return t;
|
||||||
});
|
});
|
||||||
@ -182,7 +185,7 @@ TEST_SUBMODULE(kwargs_and_defaults, m) {
|
|||||||
t[0] = o.ref_count();
|
t[0] = o.ref_count();
|
||||||
for (size_t i = 0; i < a.size(); i++) {
|
for (size_t i = 0; i < a.size(); i++) {
|
||||||
// Use raw Python API here to avoid an extra, intermediate incref on the tuple item:
|
// Use raw Python API here to avoid an extra, intermediate incref on the tuple item:
|
||||||
t[i + 1] = (int) Py_REFCNT(PyTuple_GET_ITEM(a.ptr(), static_cast<py::ssize_t>(i)));
|
t[i + 1] = REFCNT(PyTuple_GET_ITEM(a.ptr(), static_cast<py::ssize_t>(i)));
|
||||||
}
|
}
|
||||||
return t;
|
return t;
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from pybind11_tests import PYBIND11_REFCNT_IMMORTAL
|
||||||
from pybind11_tests import kwargs_and_defaults as m
|
from pybind11_tests import kwargs_and_defaults as m
|
||||||
|
|
||||||
|
|
||||||
@ -384,7 +385,7 @@ def test_args_refcount():
|
|||||||
myval = 54321
|
myval = 54321
|
||||||
expected = refcount(myval)
|
expected = refcount(myval)
|
||||||
assert m.arg_refcount_h(myval) == expected
|
assert m.arg_refcount_h(myval) == expected
|
||||||
assert m.arg_refcount_o(myval) == expected + 1
|
assert m.arg_refcount_o(myval) in {expected + 1, PYBIND11_REFCNT_IMMORTAL}
|
||||||
assert m.arg_refcount_h(myval) == expected
|
assert m.arg_refcount_h(myval) == expected
|
||||||
assert refcount(myval) == expected
|
assert refcount(myval) == expected
|
||||||
|
|
||||||
@ -420,6 +421,7 @@ def test_args_refcount():
|
|||||||
# for the `py::args`; in the previous case, we could simply inc_ref and pass on Python's input
|
# for the `py::args`; in the previous case, we could simply inc_ref and pass on Python's input
|
||||||
# tuple without having to inc_ref the individual elements, but here we can't, hence the extra
|
# tuple without having to inc_ref the individual elements, but here we can't, hence the extra
|
||||||
# refs.
|
# refs.
|
||||||
assert m.mixed_args_refcount(myval, myval, myval) == (exp3 + 3, exp3 + 3, exp3 + 3)
|
exp3_3 = PYBIND11_REFCNT_IMMORTAL if exp3 == PYBIND11_REFCNT_IMMORTAL else exp3 + 3
|
||||||
|
assert m.mixed_args_refcount(myval, myval, myval) == (exp3_3, exp3_3, exp3_3)
|
||||||
|
|
||||||
assert m.class_default_argument() == "<class 'decimal.Decimal'>"
|
assert m.class_default_argument() == "<class 'decimal.Decimal'>"
|
||||||
|
@ -5,7 +5,7 @@ import types
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import env
|
import env
|
||||||
from pybind11_tests import detailed_error_messages_enabled
|
from pybind11_tests import PYBIND11_REFCNT_IMMORTAL, detailed_error_messages_enabled
|
||||||
from pybind11_tests import pytypes as m
|
from pybind11_tests import pytypes as m
|
||||||
|
|
||||||
|
|
||||||
@ -635,7 +635,7 @@ def test_memoryview_refcount(method):
|
|||||||
ref_before = sys.getrefcount(buf)
|
ref_before = sys.getrefcount(buf)
|
||||||
view = method(buf)
|
view = method(buf)
|
||||||
ref_after = sys.getrefcount(buf)
|
ref_after = sys.getrefcount(buf)
|
||||||
assert ref_before < ref_after
|
assert ref_before < ref_after or ref_before == ref_after == PYBIND11_REFCNT_IMMORTAL
|
||||||
assert list(view) == list(buf)
|
assert list(view) == list(buf)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user