Skip to content

Commit 1e21e37

Browse files
committed
Merge branch 'master' into prepv300/review
2 parents b7cd0a3 + cc86e8b commit 1e21e37

File tree

10 files changed

+124
-44
lines changed

10 files changed

+124
-44
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ jobs:
3434
python:
3535
- '3.8'
3636
- '3.13'
37+
- '3.13t'
3738
- '3.14'
39+
- '3.14t'
3840
- 'pypy-3.10'
3941
- 'pypy-3.11'
4042
- 'graalpy-24.2'

CMakeLists.txt

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -130,28 +130,23 @@ if(PYBIND11_MASTER_PROJECT)
130130
set(Python_EXECUTABLE "${PYTHON_EXECUTABLE}")
131131
endif()
132132

133-
if(NOT DEFINED Python3_EXECUTABLE
134-
AND NOT DEFINED Python_EXECUTABLE
135-
AND NOT DEFINED Python_ROOT_DIR
136-
AND NOT DEFINED ENV{VIRTUALENV}
137-
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.venv")
138-
message(STATUS "Autodetecting Python in virtual environment")
139-
set(Python_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.venv")
140-
endif()
141-
142133
# This is a shortcut that is primarily for the venv cmake preset,
143134
# but can be used to quickly setup tests manually, too
144135
set(PYBIND11_CREATE_WITH_UV
145136
""
146-
CACHE STRING "Create a virtualenv in Python_ROOT_DIR with uv if it doesn't exist")
137+
CACHE STRING "Create a virtualenv if it doesn't exist")
147138

148139
if(NOT PYBIND11_CREATE_WITH_UV STREQUAL "")
149-
if(NOT DEFINED Python_ROOT_DIR)
150-
message(FATAL_ERROR "Python_ROOT_DIR must be defined to use PYBIND11_CREATE_WITH_UV")
151-
endif()
140+
set(Python_ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}/.venv")
152141
if(EXISTS "${Python_ROOT_DIR}")
153-
message(STATUS "Using existing venv at ${Python_ROOT_DIR}, remove to recreate")
154-
else()
142+
if(EXISTS "${CMAKE_BINARY_DIR}/CMakeCache.txt")
143+
message(STATUS "Using existing venv at ${Python_ROOT_DIR}, remove or --fresh to recreate")
144+
else()
145+
# --fresh used to remove the cache
146+
file(REMOVE_RECURSE "${CMAKE_CURRENT_BINARY_DIR}/.venv")
147+
endif()
148+
endif()
149+
if(NOT EXISTS "${Python_ROOT_DIR}")
155150
find_program(UV uv REQUIRED)
156151
# CMake 3.19+ would be able to use COMMAND_ERROR_IS_FATAL
157152
message(
@@ -172,8 +167,16 @@ if(PYBIND11_MASTER_PROJECT)
172167
message(FATAL_ERROR "uv pip install failed with '${_pip_result}'")
173168
endif()
174169
endif()
170+
else()
171+
if(NOT DEFINED Python3_EXECUTABLE
172+
AND NOT DEFINED Python_EXECUTABLE
173+
AND NOT DEFINED Python_ROOT_DIR
174+
AND NOT DEFINED ENV{VIRTUALENV}
175+
AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.venv")
176+
message(STATUS "Autodetecting Python in virtual environment")
177+
set(Python_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.venv")
178+
endif()
175179
endif()
176-
177180
endif()
178181

179182
# NB: when adding a header don't forget to also add it to setup.py

docs/advanced/classes.rst

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,13 +1384,21 @@ You can do that using ``py::custom_type_setup``:
13841384
auto *type = &heap_type->ht_type;
13851385
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
13861386
type->tp_traverse = [](PyObject *self_base, visitproc visit, void *arg) {
1387-
auto &self = py::cast<OwnsPythonObjects&>(py::handle(self_base));
1388-
Py_VISIT(self.value.ptr());
1387+
// https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_traverse
1388+
#if PY_VERSION_HEX >= 0x03090000
1389+
Py_VISIT(Py_TYPE(self_base));
1390+
#endif
1391+
if (py::detail::is_holder_constructed(self_base)) {
1392+
auto &self = py::cast<OwnsPythonObjects&>(py::handle(self_base));
1393+
Py_VISIT(self.value.ptr());
1394+
}
13891395
return 0;
13901396
};
13911397
type->tp_clear = [](PyObject *self_base) {
1392-
auto &self = py::cast<OwnsPythonObjects&>(py::handle(self_base));
1393-
self.value = py::none();
1398+
if (py::detail::is_holder_constructed(self_base)) {
1399+
auto &self = py::cast<OwnsPythonObjects&>(py::handle(self_base));
1400+
self.value = py::none();
1401+
}
13941402
return 0;
13951403
};
13961404
}));

docs/advanced/embedding.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ naturally:
212212
assert(locals["message"].cast<std::string>() == "1 + 2 = 3");
213213
}
214214
215+
``PYBIND11_EMBEDDED_MODULE`` also accepts
216+
:func:`py::mod_gil_not_used()`,
217+
:func:`py::multiple_interpreters::per_interpreter_gil()`, and
218+
:func:`py::multiple_interpreters::shared_gil()` tags just like ``PYBIND11_MODULE``.
219+
See :ref:`misc_subinterp` and :ref:`misc_free_threading` for more information.
215220

216221
Interpreter lifetime
217222
====================

docs/advanced/misc.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ following checklist.
155155
within pybind11 that will throw exceptions on certain GIL handling errors
156156
(reference counting operations).
157157

158+
.. _misc_free_threading:
159+
158160
Free-threading support
159161
==================================================================
160162

@@ -178,6 +180,8 @@ your code is thread safe. Modules must still be built against the Python free-t
178180
enable free-threading, even if they specify this tag. Adding this tag does not break
179181
compatibility with non-free-threaded Python.
180182

183+
.. _misc_subinterp:
184+
181185
Sub-interpreter support
182186
==================================================================
183187

include/pybind11/detail/value_and_holder.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,17 @@ struct value_and_holder {
7474
}
7575
};
7676

77+
// This is a semi-public API to check if the corresponding instance has been constructed with a
78+
// holder. That is, if the instance has been constructed with a holder, the `__init__` method is
79+
// called and the C++ object is valid. Otherwise, the C++ object might only be allocated, but not
80+
// initialized. This will lead to **SEGMENTATION FAULTS** if the C++ object is used in any way.
81+
// Example usage: https://pybind11.readthedocs.io/en/stable/advanced/classes.html#custom-type-setup
82+
// for `tp_traverse` and `tp_clear` implementations.
83+
// WARNING: The caller is responsible for ensuring that the `reinterpret_cast` is valid.
84+
inline bool is_holder_constructed(PyObject *obj) {
85+
auto *const instance = reinterpret_cast<pybind11::detail::instance *>(obj);
86+
return instance->get_value_and_holder().holder_constructed();
87+
}
88+
7789
PYBIND11_NAMESPACE_END(detail)
7890
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

include/pybind11/embed.h

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,24 +38,43 @@
3838
});
3939
}
4040
\endrst */
41-
#define PYBIND11_EMBEDDED_MODULE(name, variable) \
41+
PYBIND11_WARNING_PUSH
42+
PYBIND11_WARNING_DISABLE_CLANG("-Wgnu-zero-variadic-macro-arguments")
43+
#define PYBIND11_EMBEDDED_MODULE(name, variable, ...) \
4244
static ::pybind11::module_::module_def PYBIND11_CONCAT(pybind11_module_def_, name); \
45+
static ::pybind11::module_::slots_array PYBIND11_CONCAT(pybind11_module_slots_, name); \
46+
static int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject *); \
4347
static void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ &); \
4448
static PyObject PYBIND11_CONCAT(*pybind11_init_wrapper_, name)() { \
45-
auto m = ::pybind11::module_::create_extension_module( \
46-
PYBIND11_TOSTRING(name), nullptr, &PYBIND11_CONCAT(pybind11_module_def_, name)); \
49+
static auto result = []() { \
50+
auto &slots = PYBIND11_CONCAT(pybind11_module_slots_, name); \
51+
slots[0] = {Py_mod_exec, \
52+
reinterpret_cast<void *>(&PYBIND11_CONCAT(pybind11_exec_, name))}; \
53+
slots[1] = {0, nullptr}; \
54+
return ::pybind11::module_::initialize_multiphase_module_def( \
55+
PYBIND11_TOSTRING(name), \
56+
nullptr, \
57+
&PYBIND11_CONCAT(pybind11_module_def_, name), \
58+
slots, \
59+
##__VA_ARGS__); \
60+
}(); \
61+
return result.ptr(); \
62+
} \
63+
PYBIND11_EMBEDDED_MODULE_IMPL(name) \
64+
::pybind11::detail::embedded_module PYBIND11_CONCAT(pybind11_module_, name)( \
65+
PYBIND11_TOSTRING(name), PYBIND11_CONCAT(pybind11_init_impl_, name)); \
66+
int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject * pm) { \
4767
try { \
68+
auto m = pybind11::reinterpret_borrow<::pybind11::module_>(pm); \
4869
PYBIND11_CONCAT(pybind11_init_, name)(m); \
49-
return m.ptr(); \
70+
return 0; \
5071
} \
5172
PYBIND11_CATCH_INIT_EXCEPTIONS \
52-
return nullptr; \
73+
return -1; \
5374
} \
54-
PYBIND11_EMBEDDED_MODULE_IMPL(name) \
55-
::pybind11::detail::embedded_module PYBIND11_CONCAT(pybind11_module_, name)( \
56-
PYBIND11_TOSTRING(name), PYBIND11_CONCAT(pybind11_init_impl_, name)); \
5775
void PYBIND11_CONCAT(pybind11_init_, name)(::pybind11::module_ \
5876
& variable) // NOLINT(bugprone-macro-parentheses)
77+
PYBIND11_WARNING_POP
5978

6079
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
6180
PYBIND11_NAMESPACE_BEGIN(detail)

include/pybind11/pybind11.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1484,7 +1484,7 @@ class module_ : public object {
14841484
using slots_array = std::array<PyModuleDef_Slot, 4>;
14851485

14861486
/** \rst
1487-
Initialized a module def for use with multi-phase module initialization.
1487+
Initialize a module def for use with multi-phase module initialization.
14881488
14891489
``def`` should point to a statically allocated module_def.
14901490
``slots`` must already contain a Py_mod_exec or Py_mod_create slot and will be filled with

tests/test_custom_type_setup.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,21 @@ TEST_SUBMODULE(custom_type_setup, m) {
2626
auto *type = &heap_type->ht_type;
2727
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
2828
type->tp_traverse = [](PyObject *self_base, visitproc visit, void *arg) {
29-
auto &self = py::cast<OwnsPythonObjects &>(py::handle(self_base));
30-
Py_VISIT(self.value.ptr());
29+
// https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_traverse
30+
#if PY_VERSION_HEX >= 0x03090000
31+
Py_VISIT(Py_TYPE(self_base));
32+
#endif
33+
if (py::detail::is_holder_constructed(self_base)) {
34+
auto &self = py::cast<OwnsPythonObjects &>(py::handle(self_base));
35+
Py_VISIT(self.value.ptr());
36+
}
3137
return 0;
3238
};
3339
type->tp_clear = [](PyObject *self_base) {
34-
auto &self = py::cast<OwnsPythonObjects &>(py::handle(self_base));
35-
self.value = py::none();
40+
if (py::detail::is_holder_constructed(self_base)) {
41+
auto &self = py::cast<OwnsPythonObjects &>(py::handle(self_base));
42+
self.value = py::none();
43+
}
3644
return 0;
3745
};
3846
}));

tests/test_embed/test_interpreter.cpp

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class test_override_cache_helper_trampoline : public test_override_cache_helper
5555
int func() override { PYBIND11_OVERRIDE(int, test_override_cache_helper, func); }
5656
};
5757

58-
PYBIND11_EMBEDDED_MODULE(widget_module, m) {
58+
PYBIND11_EMBEDDED_MODULE(widget_module, m, py::multiple_interpreters::per_interpreter_gil()) {
5959
py::class_<Widget, PyWidget>(m, "Widget")
6060
.def(py::init<std::string>())
6161
.def_property_readonly("the_message", &Widget::the_message);
@@ -336,6 +336,7 @@ TEST_CASE("Restart the interpreter") {
336336
REQUIRE(py_widget.attr("the_message").cast<std::string>() == "Hello after restart");
337337
}
338338

339+
#if defined(PYBIND11_SUBINTERPRETER_SUPPORT)
339340
TEST_CASE("Subinterpreter") {
340341
py::module_::import("external_module"); // in the main interpreter
341342

@@ -347,6 +348,10 @@ TEST_CASE("Subinterpreter") {
347348

348349
REQUIRE(m.attr("add")(1, 2).cast<int>() == 3);
349350
}
351+
352+
auto main_int
353+
= py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>();
354+
350355
REQUIRE(has_state_dict_internals_obj());
351356
REQUIRE(has_pybind11_internals_static());
352357

@@ -359,7 +364,6 @@ TEST_CASE("Subinterpreter") {
359364
// Subinterpreters get their own copy of builtins.
360365
REQUIRE_FALSE(has_state_dict_internals_obj());
361366

362-
#if defined(PYBIND11_SUBINTERPRETER_SUPPORT) && PY_VERSION_HEX >= 0x030C0000
363367
// internals hasn't been populated yet, but will be different for the subinterpreter
364368
REQUIRE_FALSE(has_pybind11_internals_static());
365369

@@ -369,14 +373,12 @@ TEST_CASE("Subinterpreter") {
369373
py::detail::get_internals();
370374
REQUIRE(has_pybind11_internals_static());
371375
REQUIRE(get_details_as_uintptr() == ext_int);
372-
#else
373-
// This static is still defined
374-
REQUIRE(has_pybind11_internals_static());
375-
#endif
376+
REQUIRE(main_int != ext_int);
376377

377378
// Modules tags should be gone.
378379
REQUIRE_FALSE(py::hasattr(py::module_::import("__main__"), "tag"));
379380
{
381+
REQUIRE_NOTHROW(py::module_::import("widget_module"));
380382
auto m = py::module_::import("widget_module");
381383
REQUIRE_FALSE(py::hasattr(m, "extension_module_tag"));
382384

@@ -397,7 +399,6 @@ TEST_CASE("Subinterpreter") {
397399
REQUIRE(has_state_dict_internals_obj());
398400
}
399401

400-
#if defined(PYBIND11_SUBINTERPRETER_SUPPORT)
401402
TEST_CASE("Multiple Subinterpreters") {
402403
// Make sure the module is in the main interpreter and save its pointer
403404
auto *main_ext = py::module_::import("external_module").ptr();
@@ -512,10 +513,11 @@ TEST_CASE("Per-Subinterpreter GIL") {
512513

513514
// we have switched to the new interpreter and released the main gil
514515

515-
// widget_module did not provide the mod_per_interpreter_gil tag, so it cannot be imported
516+
// trampoline_module did not provide the per_interpreter_gil tag, so it cannot be
517+
// imported
516518
bool caught = false;
517519
try {
518-
py::module_::import("widget_module");
520+
py::module_::import("trampoline_module");
519521
} catch (pybind11::error_already_set &pe) {
520522
T_REQUIRE(pe.matches(PyExc_ImportError));
521523
std::string msg(pe.what());
@@ -525,6 +527,9 @@ TEST_CASE("Per-Subinterpreter GIL") {
525527
}
526528
T_REQUIRE(caught);
527529

530+
// widget_module did provide the per_interpreter_gil tag, so it this does not throw
531+
py::module_::import("widget_module");
532+
528533
T_REQUIRE(!py::hasattr(py::module_::import("external_module"), "multi_interp"));
529534
py::module_::import("external_module").attr("multi_interp") = std::to_string(num);
530535

@@ -547,8 +552,8 @@ TEST_CASE("Per-Subinterpreter GIL") {
547552

548553
Py_EndInterpreter(sub);
549554

550-
PyThreadState_Swap(
551-
main_tstate); // switch back so the scoped_acquire can release the GIL properly
555+
// switch back so the scoped_acquire can release the GIL properly
556+
PyThreadState_Swap(main_tstate);
552557
};
553558

554559
std::thread t1(thread_main, 1);
@@ -622,12 +627,26 @@ TEST_CASE("Threads") {
622627

623628
{
624629
py::gil_scoped_release gil_release{};
630+
#if defined(Py_GIL_DISABLED) && PY_VERSION_HEX < 0x030E0000
631+
std::mutex mutex;
632+
#endif
625633

626634
auto threads = std::vector<std::thread>();
627635
for (auto i = 0; i < num_threads; ++i) {
628636
threads.emplace_back([&]() {
629637
py::gil_scoped_acquire gil{};
638+
#ifdef Py_GIL_DISABLED
639+
# if PY_VERSION_HEX < 0x030E0000
640+
std::lock_guard<std::mutex> lock(mutex);
641+
locals["count"] = locals["count"].cast<int>() + 1;
642+
# else
643+
Py_BEGIN_CRITICAL_SECTION(locals.ptr());
630644
locals["count"] = locals["count"].cast<int>() + 1;
645+
Py_END_CRITICAL_SECTION();
646+
# endif
647+
#else
648+
locals["count"] = locals["count"].cast<int>() + 1;
649+
#endif
631650
});
632651
}
633652

0 commit comments

Comments
 (0)