Skip to content

Commit 37dbb55

Browse files
committed
Partial cleanup of tests. WIP. The cleanup uncovered a major problem.
1 parent 9f6094b commit 37dbb55

4 files changed

+113
-53
lines changed

include/pybind11/detail/smart_holder_poc.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ High-level aspects:
5252
#include <typeinfo>
5353

5454
//#include <iostream>
55-
inline void to_cout(std::string /*msg*/) { /*std::cout << msg << std::endl;*/ }
55+
//inline void to_cout(std::string msg) { std::cout << msg << std::endl; }
56+
inline void to_cout(std::string) { }
5657

5758
// pybindit = Python Bindings Innovation Track.
5859
// Currently not in pybind11 namespace to signal that this POC does not depend
@@ -366,7 +367,7 @@ to_cout("LOOOK smart_holder as_shared_ptr " + std::to_string(__LINE__) + " " + _
366367

367368
inline void guarded_delete::operator()(void *raw_ptr) const {
368369
if (hld) {
369-
to_cout("LOOOK guarded_delete call hld " + std::to_string(__LINE__) + " " + __FILE__);
370+
to_cout("RECLAIM guarded_delete call hld " + std::to_string(__LINE__) + " " + __FILE__);
370371
assert(armed_flag);
371372
assert(hld->vptr.get() == raw_ptr);
372373
assert(hld->vptr_is_released);

include/pybind11/detail/smart_holder_type_casters.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -393,14 +393,14 @@ to_cout("LOOOK shared_ptr_dec_ref_deleter call " + std::to_string(__LINE__) + "
393393
auto void_raw_ptr = hld.template as_raw_ptr_unowned<void>();
394394
auto type_raw_ptr = convert_type(void_raw_ptr);
395395
if (hld.pointee_depends_on_holder_owner) {
396-
auto self = reinterpret_cast<PyObject *>(load_impl.loaded_v_h.inst);
397396
auto vptr_gd_ptr = std::get_deleter<pybindit::memory::guarded_delete>(hld.vptr);
398397
if (vptr_gd_ptr != nullptr) {
399398
assert(!hld.vptr_is_released);
400399
std::shared_ptr<T> to_be_returned(hld.vptr, type_raw_ptr);
401400
std::shared_ptr<void> non_owning(
402401
hld.vptr.get(),
403402
pybindit::memory::noop_deleter_acting_as_weak_ptr_owner{hld.vptr});
403+
auto self = reinterpret_cast<PyObject *>(load_impl.loaded_v_h.inst);
404404
// Critical transfer-of-ownership section. This must stay together.
405405
vptr_gd_ptr->hld = &hld;
406406
vptr_gd_ptr->callback_ptr = dec_ref_void;
@@ -409,6 +409,7 @@ to_cout("LOOOK shared_ptr_dec_ref_deleter call " + std::to_string(__LINE__) + "
409409
hld.vptr_is_released = true;
410410
Py_INCREF(self);
411411
// Critical section end.
412+
to_cout("FRESHLY released");
412413
return to_be_returned;
413414
}
414415
auto vptr_ndaawp_ptr = std::get_deleter<
@@ -417,6 +418,7 @@ to_cout("LOOOK shared_ptr_dec_ref_deleter call " + std::to_string(__LINE__) + "
417418
assert(hld.vptr_is_released);
418419
auto released_vptr = vptr_ndaawp_ptr->passenger.lock();
419420
if (released_vptr != nullptr) {
421+
to_cout("retrieved released");
420422
return std::shared_ptr<T>(released_vptr, type_raw_ptr);
421423
}
422424
}

tests/test_class_sh_trampoline_shared_from_this.cpp

+11-10
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,7 @@ namespace {
1313
struct Sft : std::enable_shared_from_this<Sft> {
1414
std::string history;
1515
explicit Sft(const std::string &history) : history{history} {}
16-
long use_count() const {
17-
#if defined(__cpp_lib_enable_shared_from_this) && (!defined(_MSC_VER) || _MSC_VER >= 1912)
18-
return this->shared_from_this().use_count();
19-
#else
20-
return -1;
21-
#endif
22-
}
16+
long use_count() const { return this->shared_from_this().use_count() - 1; }
2317
virtual ~Sft() = default;
2418

2519
#if defined(__clang__)
@@ -50,12 +44,16 @@ struct SftSharedPtrStash {
5044
std::vector<std::shared_ptr<Sft>> stash;
5145
explicit SftSharedPtrStash(int ser_no) : ser_no{ser_no} {}
5246
void Add(const std::shared_ptr<Sft> &obj) {
53-
obj->history += "_Stash" + std::to_string(ser_no) + "Add";
47+
if (obj->history.size()) {
48+
obj->history += "_Stash" + std::to_string(ser_no) + "Add";
49+
}
5450
stash.push_back(obj);
5551
}
5652
void AddSharedFromThis(Sft *obj) {
5753
auto sft = obj->shared_from_this();
58-
sft->history += "_Stash" + std::to_string(ser_no) + "AddSharedFromThis";
54+
if (sft->history.size()) {
55+
sft->history += "_Stash" + std::to_string(ser_no) + "AddSharedFromThis";
56+
}
5957
stash.push_back(sft);
6058
}
6159
std::string history(unsigned i) {
@@ -76,7 +74,9 @@ struct SftTrampoline : Sft, py::trampoline_self_life_support {
7674

7775
long pass_shared_ptr(const std::shared_ptr<Sft> &obj) {
7876
auto sft = obj->shared_from_this();
79-
sft->history += "_PassSharedPtr";
77+
if (sft->history.size()) {
78+
sft->history += "_PassSharedPtr";
79+
}
8080
return sft.use_count();
8181
}
8282

@@ -102,4 +102,5 @@ TEST_SUBMODULE(class_sh_trampoline_shared_from_this, m) {
102102

103103
m.def("pass_shared_ptr", pass_shared_ptr);
104104
m.def("pass_unique_ptr", pass_unique_ptr);
105+
m.def("to_cout", to_cout);
105106
}

tests/test_class_sh_trampoline_shared_from_this.py

+96-40
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,89 @@
11
# -*- coding: utf-8 -*-
22
import pytest
33

4-
import env # noqa: F401
5-
64
import pybind11_tests.class_sh_trampoline_shared_from_this as m
75

8-
import gc
9-
import weakref
10-
116

127
class PySft(m.Sft):
138
pass
149

1510

16-
def test_pass_shared_ptr():
11+
def test_release_and_immediate_reclaim():
1712
obj = PySft("PySft")
1813
assert obj.history == "PySft"
19-
assert obj.use_count() in [2, -1] # TODO: Be smarter/stricter.
20-
m.pass_shared_ptr(obj)
14+
assert obj.use_count() == 1
15+
assert m.pass_shared_ptr(obj) == 2
2116
assert obj.history == "PySft_PassSharedPtr"
22-
assert obj.use_count() in [2, -1]
23-
uc = m.pass_shared_ptr(obj)
24-
assert uc == 2 # +1 for passed argument, +1 for shared_from_this.
17+
assert obj.use_count() == 1
18+
assert m.pass_shared_ptr(obj) == 2
2519
assert obj.history == "PySft_PassSharedPtr_PassSharedPtr"
26-
assert obj.use_count() in [2, -1]
20+
assert obj.use_count() == 1
21+
22+
obj = PySft("")
23+
while True:
24+
m.pass_shared_ptr(obj)
25+
assert obj.history == ""
26+
assert obj.use_count() == 1
27+
break # Comment out for manual leak checking (use `top` command).
2728

2829

29-
def test_pass_shared_ptr_while_stashed():
30+
def test_release_to_cpp_stash():
3031
obj = PySft("PySft")
31-
obj_wr = weakref.ref(obj)
3232
stash1 = m.SftSharedPtrStash(1)
3333
stash1.Add(obj)
3434
assert obj.history == "PySft_Stash1Add"
35-
assert obj.use_count() in [2, -1]
35+
assert obj.use_count() == 1
3636
assert stash1.history(0) == "PySft_Stash1Add"
3737
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.
38+
assert m.pass_shared_ptr(obj) == 3
4039
assert obj.history == "PySft_Stash1Add_PassSharedPtr"
41-
assert obj.use_count() in [2, -1]
40+
assert obj.use_count() == 1
4241
assert stash1.history(0) == "PySft_Stash1Add_PassSharedPtr"
4342
assert stash1.use_count(0) == 1
4443
stash2 = m.SftSharedPtrStash(2)
4544
stash2.Add(obj)
4645
assert obj.history == "PySft_Stash1Add_PassSharedPtr_Stash2Add"
47-
assert obj.use_count() in [3, -1]
46+
assert obj.use_count() == 2
4847
assert stash2.history(0) == "PySft_Stash1Add_PassSharedPtr_Stash2Add"
4948
assert stash2.use_count(0) == 2
5049
stash2.Add(obj)
51-
assert obj.history == "PySft_Stash1Add_PassSharedPtr_Stash2Add_Stash2Add"
52-
assert obj.use_count() in [4, -1]
50+
exp_oh = "PySft_Stash1Add_PassSharedPtr_Stash2Add_Stash2Add"
51+
assert obj.history == exp_oh
52+
assert obj.use_count() == 3
53+
assert stash1.history(0) == exp_oh
5354
assert stash1.use_count(0) == 3
54-
assert stash1.history(0) == "PySft_Stash1Add_PassSharedPtr_Stash2Add_Stash2Add"
55+
assert stash2.history(0) == exp_oh
5556
assert stash2.use_count(0) == 3
56-
assert stash2.history(0) == "PySft_Stash1Add_PassSharedPtr_Stash2Add_Stash2Add"
57+
assert stash2.history(1) == exp_oh
5758
assert stash2.use_count(1) == 3
58-
assert stash2.history(1) == "PySft_Stash1Add_PassSharedPtr_Stash2Add_Stash2Add"
5959
del obj
60+
assert stash2.history(0) == exp_oh
6061
assert stash2.use_count(0) == 3
61-
assert stash2.history(0) == "PySft_Stash1Add_PassSharedPtr_Stash2Add_Stash2Add"
62+
assert stash2.history(1) == exp_oh
6263
assert stash2.use_count(1) == 3
63-
assert stash2.history(1) == "PySft_Stash1Add_PassSharedPtr_Stash2Add_Stash2Add"
6464
del stash2
65-
gc.collect()
66-
assert obj_wr() is not None
67-
assert stash1.history(0) == "PySft_Stash1Add_PassSharedPtr_Stash2Add_Stash2Add"
68-
del stash1
69-
gc.collect()
70-
if not env.PYPY:
71-
assert obj_wr() is None
65+
assert stash1.history(0) == exp_oh
66+
assert stash1.use_count(0) == 1
7267

7368

74-
def test_pass_shared_ptr_while_stashed_with_shared_from_this():
69+
def test_release_to_cpp_stash_leak():
70+
obj = PySft("")
71+
while True:
72+
stash1 = m.SftSharedPtrStash(1)
73+
stash1.Add(obj)
74+
assert obj.history == ""
75+
assert obj.use_count() == 1
76+
assert stash1.use_count(0) == 1
77+
stash1.Add(obj)
78+
assert obj.history == ""
79+
assert obj.use_count() == 2
80+
assert stash1.use_count(0) == 2
81+
assert stash1.use_count(1) == 2
82+
break # Comment out for manual leak checking (use `top` command).
83+
84+
85+
def test_release_to_cpp_stash_via_shared_from_this():
7586
obj = PySft("PySft")
76-
obj_wr = weakref.ref(obj)
7787
stash1 = m.SftSharedPtrStash(1)
7888
stash1.AddSharedFromThis(obj)
7989
assert obj.history == "PySft_Stash1AddSharedFromThis"
@@ -82,11 +92,57 @@ def test_pass_shared_ptr_while_stashed_with_shared_from_this():
8292
assert obj.history == "PySft_Stash1AddSharedFromThis_Stash1AddSharedFromThis"
8393
assert stash1.use_count(0) == 3
8494
assert stash1.use_count(1) == 3
85-
del obj
86-
del stash1
87-
gc.collect()
88-
if not env.PYPY:
89-
assert obj_wr() is None
95+
96+
97+
def test_release_to_cpp_stash_via_shared_from_this_leak_1(): # WIP
98+
m.to_cout("")
99+
m.to_cout("")
100+
m.to_cout("Add first")
101+
obj = PySft("")
102+
import weakref
103+
104+
obj_wr = weakref.ref(obj)
105+
while True:
106+
stash1 = m.SftSharedPtrStash(1)
107+
stash1.Add(obj)
108+
assert obj.history == ""
109+
assert obj.use_count() == 1
110+
assert stash1.use_count(0) == 1
111+
stash1.AddSharedFromThis(obj)
112+
assert obj.history == ""
113+
assert obj.use_count() == 2
114+
assert stash1.use_count(0) == 2
115+
assert stash1.use_count(1) == 2
116+
del obj
117+
assert obj_wr() is not None
118+
assert stash1.use_count(0) == 2
119+
assert stash1.use_count(1) == 2
120+
break # Comment out for manual leak checking (use `top` command).
121+
122+
123+
def test_release_to_cpp_stash_via_shared_from_this_leak_2(): # WIP
124+
m.to_cout("")
125+
m.to_cout("AddSharedFromThis only")
126+
obj = PySft("")
127+
import weakref
128+
129+
obj_wr = weakref.ref(obj)
130+
while True:
131+
stash1 = m.SftSharedPtrStash(1)
132+
stash1.AddSharedFromThis(obj)
133+
assert obj.history == ""
134+
assert obj.use_count() == 2
135+
assert stash1.use_count(0) == 2
136+
stash1.AddSharedFromThis(obj)
137+
assert obj.history == ""
138+
assert obj.use_count() == 3
139+
assert stash1.use_count(0) == 3
140+
assert stash1.use_count(1) == 3
141+
del obj
142+
assert obj_wr() is None # BAD NEEDS FIXING
143+
assert stash1.use_count(0) == 2
144+
assert stash1.use_count(1) == 2
145+
break # Comment out for manual leak checking (use `top` command).
90146

91147

92148
def test_pass_released_shared_ptr_as_unique_ptr():

0 commit comments

Comments
 (0)