Skip to content

Commit d7117a4

Browse files
committed
tweaks to PR #549 and miscellaneous cleanups
This commit splits up a large testcase into smaller ones, which fixes a weird GC issue that seems to be specific to Python on Windows (instances being collected too late, which makes a test flaky).
1 parent c36584e commit d7117a4

File tree

5 files changed

+32
-27
lines changed

5 files changed

+32
-27
lines changed

include/nanobind/stl/unique_ptr.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,16 +155,18 @@ struct type_caster<std::unique_ptr<T, Deleter>> {
155155
}
156156

157157
explicit operator Value() {
158-
if (inflight)
159-
inflight = false;
160-
else if (!nb_type_relinquish_ownership(src.ptr(), IsDefaultDeleter))
158+
if (!inflight && !nb_type_relinquish_ownership(src.ptr(), IsDefaultDeleter))
161159
throw next_overload();
162160

163-
T *value = caster.operator T *();
161+
T *p = caster.operator T *();
162+
163+
Value value;
164164
if constexpr (IsNanobindDeleter)
165-
return Value(value, deleter<T>(src.inc_ref()));
165+
value = Value(p, deleter<T>(src.inc_ref()));
166166
else
167-
return Value(value);
167+
value = Value(p);
168+
inflight = false;
169+
return value;
168170
}
169171
};
170172

src/nb_enum.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ bool enum_from_python(const std::type_info *tp, PyObject *o, int64_t *out, uint8
166166
PyErr_Clear();
167167
return false;
168168
}
169-
enum_map::iterator it = fwd->find((int64_t) value);
170-
if (it != fwd->end()) {
169+
enum_map::iterator it2 = fwd->find((int64_t) value);
170+
if (it2 != fwd->end()) {
171171
*out = (int64_t) value;
172172
return true;
173173
}
@@ -177,8 +177,8 @@ bool enum_from_python(const std::type_info *tp, PyObject *o, int64_t *out, uint8
177177
PyErr_Clear();
178178
return false;
179179
}
180-
enum_map::iterator it = fwd->find((int64_t) value);
181-
if (it != fwd->end()) {
180+
enum_map::iterator it2 = fwd->find((int64_t) value);
181+
if (it2 != fwd->end()) {
182182
*out = (int64_t) value;
183183
return true;
184184
}

src/nb_type.cpp

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,18 +1677,17 @@ static void warn_relinquish_failed(const char *why, PyObject *o) noexcept {
16771677
bool nb_type_relinquish_ownership(PyObject *o, bool cpp_delete) noexcept {
16781678
nb_inst *inst = (nb_inst *) o;
16791679

1680-
/* This function is called after nb_type_get() succeeds, so the
1681-
instance should be ready; but the !ready case is possible if
1682-
an attempt is made to transfer ownership of the same object
1683-
to C++ multiple times as part of the same data structure.
1684-
For example, converting Python (foo, foo) to C++
1680+
/* This function is called after nb_type_get() succeeds, so the instance
1681+
should be ready; but the !ready case is possible if an attempt is made to
1682+
transfer ownership of the same object to C++ multiple times as part of
1683+
the same data structure. For example, converting Python (foo, foo) to C++
16851684
std::pair<std::unique_ptr<T>, std::unique_ptr<T>>. */
16861685

16871686
if (!inst->ready) {
16881687
warn_relinquish_failed(
1689-
"The resulting data structure would have had multiple "
1690-
"std::unique_ptrs that each thought they owned the same "
1691-
"Python instance's data, which is not allowed.", o);
1688+
"The resulting data structure would have multiple "
1689+
"std::unique_ptrs, each thinking that they own the same instance, "
1690+
"which is not allowed.", o);
16921691
return false;
16931692
}
16941693

@@ -1698,8 +1697,8 @@ bool nb_type_relinquish_ownership(PyObject *o, bool cpp_delete) noexcept {
16981697
"This is only possible when the instance was previously "
16991698
"constructed on the C++ side and is now owned by Python, which "
17001699
"was not the case here. You could change the unique pointer "
1701-
"signature to std::unique_ptr<T, nb::deleter<T>> to work around "
1702-
"this issue.", o);
1700+
"signature to std::unique_ptr<T, nb::deleter<T>> to work "
1701+
"around this issue.", o);
17031702
return false;
17041703
}
17051704

tests/test_holders.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,8 @@ NB_MODULE(test_holders_ext, m) {
177177
[](std::vector<std::pair<std::unique_ptr<Example>,
178178
std::unique_ptr<Example>>> v,
179179
bool clear) {
180-
if (clear) {
180+
if (clear)
181181
v.clear();
182-
}
183182
return v;
184183
}, nb::arg("v"), nb::arg("clear") = false);
185184

tests/test_holders.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ def test04_uniqueptr_from_cpp(clean):
8585
assert t.stats() == (2, 2)
8686

8787

88-
def test05_uniqueptr_from_cpp(clean):
88+
def test05a_uniqueptr_from_cpp(clean):
8989
# Test ownership exchange when the object has been created on the C++ side
9090
a = t.unique_from_cpp()
9191
b = t.unique_from_cpp_2()
@@ -121,6 +121,8 @@ def test05_uniqueptr_from_cpp(clean):
121121
collect()
122122
assert t.stats() == (2, 2)
123123

124+
def test05b_uniqueptr_list(clean):
125+
t.reset()
124126
# Test ownership exchange as part of a larger data structure
125127
k = t.unique_from_cpp(1)
126128
v = t.unique_from_cpp(2)
@@ -134,20 +136,23 @@ def test05_uniqueptr_from_cpp(clean):
134136
with pytest.warns(RuntimeWarning, match='nanobind: attempted to access an uninitialized instance of type \'test_holders_ext.Example\'!'):
135137
with pytest.raises(TypeError) as excinfo:
136138
obj.value
139+
collect()
140+
assert t.stats() == (2, 2)
137141

142+
def test05c_uniqueptr_structure_duplicate(clean):
143+
t.reset()
138144
# Test ownership exchange that fails partway through
139-
# (can't take ownershp from k twice)
145+
# (can't take ownership from k twice)
140146
k = t.unique_from_cpp(3)
141147
with pytest.warns(RuntimeWarning, match=r'nanobind::detail::nb_relinquish_ownership()'):
142148
with pytest.raises(TypeError):
143149
t.passthrough_unique_pairs([(k, k)])
144150
# Ownership passes back to Python
145151
assert k.value == 3
146152

147-
del k, v, res
148-
collect()
153+
del k
149154
collect()
150-
assert t.stats() == (5, 5)
155+
assert t.stats() == (1, 1)
151156

152157

153158
def test06_uniqueptr_from_py(clean):

0 commit comments

Comments
 (0)