Skip to content

Commit dbd7947

Browse files
committed
Merge branch 'master' into sh_merge_master
2 parents 449f413 + 15d9dae commit dbd7947

File tree

6 files changed

+103
-8
lines changed

6 files changed

+103
-8
lines changed

Diff for: .pre-commit-config.yaml

+4-4
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,22 @@ repos:
2525

2626
# Clang format the codebase automatically
2727
- repo: https://github.com/pre-commit/mirrors-clang-format
28-
rev: "v19.1.4"
28+
rev: "v19.1.6"
2929
hooks:
3030
- id: clang-format
3131
types_or: [c++, c, cuda]
3232

3333
# Ruff, the Python auto-correcting linter/formatter written in Rust
3434
- repo: https://github.com/astral-sh/ruff-pre-commit
35-
rev: v0.8.1
35+
rev: v0.8.6
3636
hooks:
3737
- id: ruff
3838
args: ["--fix", "--show-fixes"]
3939
- id: ruff-format
4040

4141
# Check static types with mypy
4242
- repo: https://github.com/pre-commit/mirrors-mypy
43-
rev: "v1.13.0"
43+
rev: "v1.14.1"
4444
hooks:
4545
- id: mypy
4646
args: []
@@ -144,7 +144,7 @@ repos:
144144

145145
# PyLint has native support - not always usable, but works for us
146146
- repo: https://github.com/PyCQA/pylint
147-
rev: "v3.3.2"
147+
rev: "v3.3.3"
148148
hooks:
149149
- id: pylint
150150
files: ^pybind11

Diff for: docs/requirements.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,9 @@ imagesize==1.4.1 \
130130
--hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \
131131
--hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a
132132
# via sphinx
133-
jinja2==3.1.4 \
134-
--hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \
135-
--hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d
133+
jinja2==3.1.5 \
134+
--hash=sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb \
135+
--hash=sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb
136136
# via sphinx
137137
markupsafe==2.1.5 \
138138
--hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \

Diff for: include/pybind11/detail/class.h

+24
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,31 @@ inline void traverse_offset_bases(void *valueptr,
312312
}
313313
}
314314

315+
#ifdef Py_GIL_DISABLED
316+
inline void enable_try_inc_ref(PyObject *obj) {
317+
// TODO: Replace with PyUnstable_Object_EnableTryIncRef when available.
318+
// See https://github.com/python/cpython/issues/128844
319+
if (_Py_IsImmortal(obj)) {
320+
return;
321+
}
322+
for (;;) {
323+
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared);
324+
if ((shared & _Py_REF_SHARED_FLAG_MASK) != 0) {
325+
// Nothing to do if it's in WEAKREFS, QUEUED, or MERGED states.
326+
return;
327+
}
328+
if (_Py_atomic_compare_exchange_ssize(
329+
&obj->ob_ref_shared, &shared, shared | _Py_REF_MAYBE_WEAKREF)) {
330+
return;
331+
}
332+
}
333+
}
334+
#endif
335+
315336
inline bool register_instance_impl(void *ptr, instance *self) {
337+
#ifdef Py_GIL_DISABLED
338+
enable_try_inc_ref(reinterpret_cast<PyObject *>(self));
339+
#endif
316340
with_instance_map(ptr, [&](instance_map &instances) { instances.emplace(ptr, self); });
317341
return true; // unused, but gives the same signature as the deregister func
318342
}

Diff for: include/pybind11/detail/type_caster_base.h

+47-1
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,49 @@ PYBIND11_NOINLINE handle get_type_handle(const std::type_info &tp, bool throw_if
245245
return handle(type_info ? ((PyObject *) type_info->type) : nullptr);
246246
}
247247

248+
inline bool try_incref(PyObject *obj) {
249+
// Tries to increment the reference count of an object if it's not zero.
250+
// TODO: Use PyUnstable_TryIncref when available.
251+
// See https://github.com/python/cpython/issues/128844
252+
#ifdef Py_GIL_DISABLED
253+
// See
254+
// https://github.com/python/cpython/blob/d05140f9f77d7dfc753dd1e5ac3a5962aaa03eff/Include/internal/pycore_object.h#L761
255+
uint32_t local = _Py_atomic_load_uint32_relaxed(&obj->ob_ref_local);
256+
local += 1;
257+
if (local == 0) {
258+
// immortal
259+
return true;
260+
}
261+
if (_Py_IsOwnedByCurrentThread(obj)) {
262+
_Py_atomic_store_uint32_relaxed(&obj->ob_ref_local, local);
263+
# ifdef Py_REF_DEBUG
264+
_Py_INCREF_IncRefTotal();
265+
# endif
266+
return true;
267+
}
268+
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&obj->ob_ref_shared);
269+
for (;;) {
270+
// If the shared refcount is zero and the object is either merged
271+
// or may not have weak references, then we cannot incref it.
272+
if (shared == 0 || shared == _Py_REF_MERGED) {
273+
return false;
274+
}
275+
276+
if (_Py_atomic_compare_exchange_ssize(
277+
&obj->ob_ref_shared, &shared, shared + (1 << _Py_REF_SHARED_SHIFT))) {
278+
# ifdef Py_REF_DEBUG
279+
_Py_INCREF_IncRefTotal();
280+
# endif
281+
return true;
282+
}
283+
}
284+
#else
285+
assert(Py_REFCNT(obj) > 0);
286+
Py_INCREF(obj);
287+
return true;
288+
#endif
289+
}
290+
248291
// Searches the inheritance graph for a registered Python instance, using all_type_info().
249292
PYBIND11_NOINLINE handle find_registered_python_instance(void *src,
250293
const detail::type_info *tinfo) {
@@ -253,7 +296,10 @@ PYBIND11_NOINLINE handle find_registered_python_instance(void *src,
253296
for (auto it_i = it_instances.first; it_i != it_instances.second; ++it_i) {
254297
for (auto *instance_type : detail::all_type_info(Py_TYPE(it_i->second))) {
255298
if (instance_type && same_type(*instance_type->cpptype, *tinfo->cpptype)) {
256-
return handle((PyObject *) it_i->second).inc_ref();
299+
auto *wrapper = reinterpret_cast<PyObject *>(it_i->second);
300+
if (try_incref(wrapper)) {
301+
return handle(wrapper);
302+
}
257303
}
258304
}
259305
}

Diff for: tests/test_thread.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ struct IntStruct {
2828
int value;
2929
};
3030

31+
struct EmptyStruct {};
32+
EmptyStruct SharedInstance;
33+
3134
} // namespace
3235

3336
TEST_SUBMODULE(thread, m) {
@@ -61,6 +64,9 @@ TEST_SUBMODULE(thread, m) {
6164
},
6265
py::call_guard<py::gil_scoped_release>());
6366

67+
py::class_<EmptyStruct>(m, "EmptyStruct")
68+
.def_readonly_static("SharedInstance", &SharedInstance);
69+
6470
// NOTE: std::string_view also uses loader_life_support to ensure that
6571
// the string contents remain alive, but that's a C++ 17 feature.
6672
}

Diff for: tests/test_thread.py

+19
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,22 @@ def test_implicit_conversion_no_gil():
4747
x.start()
4848
for x in [c, b, a]:
4949
x.join()
50+
51+
52+
@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads")
53+
def test_bind_shared_instance():
54+
nb_threads = 4
55+
b = threading.Barrier(nb_threads)
56+
57+
def access_shared_instance():
58+
b.wait()
59+
for _ in range(1000):
60+
m.EmptyStruct.SharedInstance # noqa: B018
61+
62+
threads = [
63+
threading.Thread(target=access_shared_instance) for _ in range(nb_threads)
64+
]
65+
for thread in threads:
66+
thread.start()
67+
for thread in threads:
68+
thread.join()

0 commit comments

Comments
 (0)