diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3a614b6b7..a5fa5a5e5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,14 +25,14 @@ repos: # Clang format the codebase automatically - repo: https://github.com/pre-commit/mirrors-clang-format - rev: "v18.1.8" + rev: "v19.1.3" hooks: - id: clang-format types_or: [c++, c, cuda] # Ruff, the Python auto-correcting linter/formatter written in Rust - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.3 + rev: v0.7.2 hooks: - id: ruff args: ["--fix", "--show-fixes"] @@ -40,7 +40,7 @@ repos: # Check static types with mypy - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.11.2" + rev: "v1.13.0" hooks: - id: mypy args: [] @@ -62,7 +62,7 @@ repos: # Standard hooks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: "v4.6.0" + rev: "v5.0.0" hooks: - id: check-added-large-files - id: check-case-conflict @@ -80,7 +80,7 @@ repos: # Also code format the docs - repo: https://github.com/adamchainz/blacken-docs - rev: "1.18.0" + rev: "1.19.1" hooks: - id: blacken-docs additional_dependencies: @@ -110,7 +110,7 @@ repos: # Checks the manifest for missing files (native support) - repo: https://github.com/mgedmin/check-manifest - rev: "0.49" + rev: "0.50" hooks: - id: check-manifest # This is a slow hook, so only run this if --hook-stage manual is passed @@ -144,14 +144,14 @@ repos: # PyLint has native support - not always usable, but works for us - repo: https://github.com/PyCQA/pylint - rev: "v3.2.7" + rev: "v3.3.1" hooks: - id: pylint files: ^pybind11 # Check schemas on some of our YAML files - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.29.2 + rev: 0.29.4 hooks: - id: check-readthedocs - id: check-github-workflows diff --git a/docs/upgrade.rst b/docs/upgrade.rst index 17c26aaa9..5cef2b81a 100644 --- a/docs/upgrade.rst +++ b/docs/upgrade.rst @@ -24,7 +24,8 @@ changes are that: function is not available anymore. Due to NumPy changes, you may experience difficulties updating to NumPy 2. -Please see the [NumPy 2 migration guide](https://numpy.org/devdocs/numpy_2_0_migration_guide.html) for details. +Please see the `NumPy 2 migration guide `_ +for details. For example, a more direct change could be that the default integer ``"int_"`` (and ``"uint"``) is now ``ssize_t`` and not ``long`` (affects 64bit windows). diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 2746e6016..3e640b206 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -1150,14 +1150,14 @@ struct overload_cast_impl { } template - constexpr auto operator()(Return (Class::*pmf)(Args...), - std::false_type = {}) const noexcept -> decltype(pmf) { + constexpr auto operator()(Return (Class::*pmf)(Args...), std::false_type = {}) const noexcept + -> decltype(pmf) { return pmf; } template - constexpr auto operator()(Return (Class::*pmf)(Args...) const, - std::true_type) const noexcept -> decltype(pmf) { + constexpr auto operator()(Return (Class::*pmf)(Args...) const, std::true_type) const noexcept + -> decltype(pmf) { return pmf; } }; diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 7d546311e..635614b0d 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -156,9 +156,8 @@ constexpr auto concat(const descr &d, const Args &...args) { } #else template -constexpr auto concat(const descr &d, - const Args &...args) -> decltype(std::declval>() - + concat(args...)) { +constexpr auto concat(const descr &d, const Args &...args) + -> decltype(std::declval>() + concat(args...)) { return d + const_name(", ") + concat(args...); } #endif diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index bbfa1afff..0843d8273 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -697,8 +697,8 @@ inline std::uint64_t mix64(std::uint64_t z) { } template -inline auto with_instance_map(const void *ptr, - const F &cb) -> decltype(cb(std::declval())) { +inline auto with_instance_map(const void *ptr, const F &cb) + -> decltype(cb(std::declval())) { auto &internals = get_internals(); #ifdef Py_GIL_DISABLED diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index faaa07c56..217de49e0 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -121,7 +121,6 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector(t->tp_bases)) { check.push_back((PyTypeObject *) parent.ptr()); } - auto const &type_dict = get_internals().registered_types_py; for (size_t i = 0; i < check.size(); i++) { auto *type = check[i]; @@ -180,13 +179,7 @@ PYBIND11_NOINLINE void all_type_info_populate(PyTypeObject *t, std::vector &all_type_info(PyTypeObject *type) { - auto ins = all_type_info_get_cache(type); - if (ins.second) { - // New cache entry: populate it - all_type_info_populate(type, ins.first->second); - } - - return ins.first->second; + return all_type_info_get_cache(type).first->second; } /** @@ -1535,14 +1528,14 @@ protected: does not have a private operator new implementation. A comma operator is used in the decltype argument to apply SFINAE to the public copy/move constructors.*/ template ::value>> - static auto make_copy_constructor(const T *) -> decltype(new T(std::declval()), - Constructor{}) { + static auto make_copy_constructor(const T *) + -> decltype(new T(std::declval()), Constructor{}) { return [](const void *arg) -> void * { return new T(*reinterpret_cast(arg)); }; } template ::value>> - static auto make_move_constructor(const T *) -> decltype(new T(std::declval()), - Constructor{}) { + static auto make_move_constructor(const T *) + -> decltype(new T(std::declval()), Constructor{}) { return [](const void *arg) -> void * { return new T(std::move(*const_cast(reinterpret_cast(arg)))); }; diff --git a/include/pybind11/eigen/tensor.h b/include/pybind11/eigen/tensor.h index 0a9d7c252..9b5d9e89b 100644 --- a/include/pybind11/eigen/tensor.h +++ b/include/pybind11/eigen/tensor.h @@ -124,9 +124,9 @@ struct eigen_tensor_helper< template struct get_tensor_descriptor { static constexpr auto details - = const_name(", flags.writeable", "") - + const_name(Type::Layout) == static_cast(Eigen::RowMajor)>( - ", flags.c_contiguous", ", flags.f_contiguous"); + = const_name(", flags.writeable", "") + const_name + < static_cast(Type::Layout) + == static_cast(Eigen::RowMajor) > (", flags.c_contiguous", ", flags.f_contiguous"); static constexpr auto value = const_name("numpy.ndarray[") + npy_format_descriptor::name + const_name("[") + eigen_tensor_helper>::dimensions_descriptor diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 7bc5354ea..3eac90d2a 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -2665,13 +2665,20 @@ keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret) { inline std::pair all_type_info_get_cache(PyTypeObject *type) { auto res = with_internals([type](internals &internals) { - return internals - .registered_types_py + auto ins = internals + .registered_types_py #ifdef __cpp_lib_unordered_map_try_emplace - .try_emplace(type); + .try_emplace(type); #else - .emplace(type, std::vector()); + .emplace(type, std::vector()); #endif + if (ins.second) { + // For free-threading mode, this call must be under + // the with_internals() mutex lock, to avoid that other threads + // continue running with the empty ins.first->second. + all_type_info_populate(type, ins.first->second); + } + return ins; }); if (res.second) { // New cache entry created; set up a weak reference to automatically remove it if the type diff --git a/tests/pybind11_tests.cpp b/tests/pybind11_tests.cpp index 3d2d84e77..818d53a54 100644 --- a/tests/pybind11_tests.cpp +++ b/tests/pybind11_tests.cpp @@ -128,4 +128,9 @@ PYBIND11_MODULE(pybind11_tests, m, py::mod_gil_not_used()) { for (const auto &initializer : initializers()) { initializer(m); } + + py::class_(m, "TestContext") + .def(py::init<>(&TestContext::createNewContextForInit)) + .def("__enter__", &TestContext::contextEnter) + .def("__exit__", &TestContext::contextExit); } diff --git a/tests/pybind11_tests.h b/tests/pybind11_tests.h index 7be58feb6..0eb0398df 100644 --- a/tests/pybind11_tests.h +++ b/tests/pybind11_tests.h @@ -96,3 +96,24 @@ void ignoreOldStyleInitWarnings(F &&body) { )", py::dict(py::arg("body") = py::cpp_function(body))); } + +// See PR #5419 for background. +class TestContext { +public: + TestContext() = delete; + TestContext(const TestContext &) = delete; + TestContext(TestContext &&) = delete; + static TestContext *createNewContextForInit() { return new TestContext("new-context"); } + + pybind11::object contextEnter() { + py::object contextObj = py::cast(*this); + return contextObj; + } + void contextExit(const pybind11::object & /*excType*/, + const pybind11::object & /*excVal*/, + const pybind11::object & /*excTb*/) {} + +private: + explicit TestContext(const std::string &context) : context(context) {} + std::string context; +}; diff --git a/tests/test_class.py b/tests/test_class.py index b6d97dddd..44a558247 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -1,5 +1,6 @@ from __future__ import annotations +import sys from unittest import mock import pytest @@ -520,3 +521,31 @@ def test_pr4220_tripped_over_this(): m.Empty0().get_msg() == "This is really only meant to exercise successful compilation." ) + + +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") +def test_all_type_info_multithreaded(): + # See PR #5419 for background. + import threading + + from pybind11_tests import TestContext + + class Context(TestContext): + pass + + num_runs = 10 + num_threads = 4 + barrier = threading.Barrier(num_threads) + + def func(): + barrier.wait() + with Context(): + pass + + for _ in range(num_runs): + threads = [threading.Thread(target=func) for _ in range(num_threads)] + for thread in threads: + thread.start() + + for thread in threads: + thread.join()