Skip to content

Commit 9f6094b

Browse files
committed
Leveraging new noop_deleter_acting_as_weak_ptr_owner to retrieve released vptr.
The placeholder `vptr` is never exposed anymore, therefore the externally visible `use_count`s are more intuitive.
1 parent b000575 commit 9f6094b

File tree

4 files changed

+47
-27
lines changed

4 files changed

+47
-27
lines changed

include/pybind11/detail/smart_holder_poc.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ guarded_delete make_guarded_custom_deleter(bool armed_flag) {
123123
return guarded_delete(shd_ptr_reset<T>, custom_delete<T, D>, armed_flag);
124124
};
125125

126+
// Trick to keep the smart_holder memory footprint small.
127+
struct noop_deleter_acting_as_weak_ptr_owner {
128+
std::weak_ptr<void> passenger;
129+
void operator()(void *) {};
130+
};
131+
126132
template <typename T>
127133
inline bool is_std_default_delete(const std::type_info &rtti_deleter) {
128134
return rtti_deleter == typeid(std::default_delete<T>)

include/pybind11/detail/smart_holder_type_casters.h

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -394,35 +394,41 @@ to_cout("LOOOK shared_ptr_dec_ref_deleter call " + std::to_string(__LINE__) + "
394394
auto type_raw_ptr = convert_type(void_raw_ptr);
395395
if (hld.pointee_depends_on_holder_owner) {
396396
auto self = reinterpret_cast<PyObject *>(load_impl.loaded_v_h.inst);
397-
auto vptr_del_ptr = std::get_deleter<pybindit::memory::guarded_delete>(hld.vptr);
398-
if (vptr_del_ptr != nullptr) {
397+
auto vptr_gd_ptr = std::get_deleter<pybindit::memory::guarded_delete>(hld.vptr);
398+
if (vptr_gd_ptr != nullptr) {
399399
assert(!hld.vptr_is_released);
400400
std::shared_ptr<T> to_be_returned(hld.vptr, type_raw_ptr);
401-
int sftstat = pybindit::memory::shared_from_this_status(type_raw_ptr);
402-
to_cout("LOOOK loaded_as_shared_ptr return released SFT=" + std::to_string(sftstat) + " #1 " + std::to_string(__LINE__) + " " + __FILE__);
403-
std::shared_ptr<void> non_owning(hld.vptr.get(), [](void *) {});
401+
std::shared_ptr<void> non_owning(
402+
hld.vptr.get(),
403+
pybindit::memory::noop_deleter_acting_as_weak_ptr_owner{hld.vptr});
404404
// Critical transfer-of-ownership section. This must stay together.
405-
vptr_del_ptr->hld = &hld;
406-
vptr_del_ptr->callback_ptr = dec_ref_void;
407-
vptr_del_ptr->callback_arg = self;
405+
vptr_gd_ptr->hld = &hld;
406+
vptr_gd_ptr->callback_ptr = dec_ref_void;
407+
vptr_gd_ptr->callback_arg = self;
408408
hld.vptr = non_owning;
409409
hld.vptr_is_released = true;
410410
Py_INCREF(self);
411411
// Critical section end.
412-
sftstat = pybindit::memory::shared_from_this_status(type_raw_ptr);
413-
to_cout("LOOOK loaded_as_shared_ptr return released SFT=" + std::to_string(sftstat) + " #2 " + std::to_string(__LINE__) + " " + __FILE__);
414412
return to_be_returned;
415413
}
416-
// XXX XXX XXX Ensure not shared_from_this.
417-
Py_INCREF(self);
418-
to_cout("LOOOK loaded_as_shared_ptr return shared_ptr_dec_ref_deleter " + std::to_string(__LINE__) + " " + __FILE__);
419-
return std::shared_ptr<T>(type_raw_ptr, shared_ptr_dec_ref_deleter{self});
414+
auto vptr_ndaawp_ptr = std::get_deleter<
415+
pybindit::memory::noop_deleter_acting_as_weak_ptr_owner>(hld.vptr);
416+
if (vptr_ndaawp_ptr != nullptr) {
417+
assert(hld.vptr_is_released);
418+
auto released_vptr = vptr_ndaawp_ptr->passenger.lock();
419+
if (released_vptr != nullptr) {
420+
return std::shared_ptr<T>(released_vptr, type_raw_ptr);
421+
}
422+
}
423+
pybind11_fail(
424+
"smart_holder_type_casters: loaded_as_shared_ptr failure:"
425+
" fatal internal inconsistency.");
420426
}
421427
if (hld.vptr_is_using_noop_deleter) {
422428
throw std::runtime_error("Non-owning holder (loaded_as_shared_ptr).");
423429
}
430+
assert(!hld.vptr_is_released);
424431
std::shared_ptr<void> void_shd_ptr = hld.template as_shared_ptr<void>();
425-
to_cout("LOOOK loaded_as_shared_ptr return hld vptr " + std::to_string(__LINE__) + " " + __FILE__);
426432
return std::shared_ptr<T>(void_shd_ptr, type_raw_ptr);
427433
}
428434

tests/test_class_sh_trampoline_shared_from_this.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,10 @@ struct SftTrampoline : Sft, py::trampoline_self_life_support {
7474
using Sft::Sft;
7575
};
7676

77-
void pass_shared_ptr(const std::shared_ptr<Sft> &obj) {
78-
obj->shared_from_this()->history += "_PassSharedPtr";
77+
long pass_shared_ptr(const std::shared_ptr<Sft> &obj) {
78+
auto sft = obj->shared_from_this();
79+
sft->history += "_PassSharedPtr";
80+
return sft.use_count();
7981
}
8082

8183
void pass_unique_ptr(const std::unique_ptr<Sft> &) {}

tests/test_class_sh_trampoline_shared_from_this.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ def test_pass_shared_ptr():
2020
m.pass_shared_ptr(obj)
2121
assert obj.history == "PySft_PassSharedPtr"
2222
assert obj.use_count() in [2, -1]
23-
m.pass_shared_ptr(obj)
23+
uc = m.pass_shared_ptr(obj)
24+
assert uc == 2 # +1 for passed argument, +1 for shared_from_this.
2425
assert obj.history == "PySft_PassSharedPtr_PassSharedPtr"
2526
assert obj.use_count() in [2, -1]
2627

@@ -32,28 +33,33 @@ def test_pass_shared_ptr_while_stashed():
3233
stash1.Add(obj)
3334
assert obj.history == "PySft_Stash1Add"
3435
assert obj.use_count() in [2, -1]
35-
m.pass_shared_ptr(obj)
36+
assert stash1.history(0) == "PySft_Stash1Add"
37+
assert stash1.use_count(0) == 1 # obj does NOT own the shared_ptr anymore.
38+
uc = m.pass_shared_ptr(obj)
39+
assert uc == 3 # +1 for passed argument, +1 for shared_from_this.
3640
assert obj.history == "PySft_Stash1Add_PassSharedPtr"
3741
assert obj.use_count() in [2, -1]
42+
assert stash1.history(0) == "PySft_Stash1Add_PassSharedPtr"
43+
assert stash1.use_count(0) == 1
3844
stash2 = m.SftSharedPtrStash(2)
3945
stash2.Add(obj)
4046
assert obj.history == "PySft_Stash1Add_PassSharedPtr_Stash2Add"
41-
assert obj.use_count() in [2, -1]
47+
assert obj.use_count() in [3, -1]
4248
assert stash2.history(0) == "PySft_Stash1Add_PassSharedPtr_Stash2Add"
43-
assert stash2.use_count(0) == 1 # TODO: this is not great.
49+
assert stash2.use_count(0) == 2
4450
stash2.Add(obj)
4551
assert obj.history == "PySft_Stash1Add_PassSharedPtr_Stash2Add_Stash2Add"
46-
assert obj.use_count() in [2, -1]
47-
assert stash1.use_count(0) == 1
52+
assert obj.use_count() in [4, -1]
53+
assert stash1.use_count(0) == 3
4854
assert stash1.history(0) == "PySft_Stash1Add_PassSharedPtr_Stash2Add_Stash2Add"
49-
assert stash2.use_count(0) == 1
55+
assert stash2.use_count(0) == 3
5056
assert stash2.history(0) == "PySft_Stash1Add_PassSharedPtr_Stash2Add_Stash2Add"
51-
assert stash2.use_count(1) == 1
57+
assert stash2.use_count(1) == 3
5258
assert stash2.history(1) == "PySft_Stash1Add_PassSharedPtr_Stash2Add_Stash2Add"
5359
del obj
54-
assert stash2.use_count(0) == 1
60+
assert stash2.use_count(0) == 3
5561
assert stash2.history(0) == "PySft_Stash1Add_PassSharedPtr_Stash2Add_Stash2Add"
56-
assert stash2.use_count(1) == 1
62+
assert stash2.use_count(1) == 3
5763
assert stash2.history(1) == "PySft_Stash1Add_PassSharedPtr_Stash2Add_Stash2Add"
5864
del stash2
5965
gc.collect()

0 commit comments

Comments
 (0)