-
Notifications
You must be signed in to change notification settings - Fork 2.2k
[smart_holder] .def_readonly, .def_readwrite adaptors #3581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
2ca30c8
to
0b821ba
Compare
To track progress (FYI @wangxf123456): I've verified that the PyCLIF TODO here can be removed when using this PR (ASAN clean). a = nested_fields.AA()
a.b.c.i = 123
c = a.b.c
- if 'pybind11' not in nested_fields.__doc__:
- with self.assertRaises(ValueError):
- # Cannot give up |a| as |c| still is alive
- nested_fields.ConsumeAA(a)
- del c
- # Since |c| is now gone, we can give up |a|
+ with self.assertRaises(ValueError):
+ # Cannot give up |a| as |c| still is alive
nested_fields.ConsumeAA(a)
- else:
- # TODO(rwgk): This disowns |a| even though |c| is still alive. Accessing
- # |c| beyond this point generates an ASAN heap-use-after-free error.
- nested_fields.ConsumeAA(a)
+ del c
+ # Since |c| is now gone, we can give up |a|
+ nested_fields.ConsumeAA(a)
a = nested_fields.AA()
a.b.c.i = 123 |
include/pybind11/pybind11.h
Outdated
detail::enable_if_t<detail::type_uses_smart_holder_type_caster<T>::value | ||
&& detail::type_uses_smart_holder_type_caster<D>::value | ||
&& !std::is_pointer<D>::value // | ||
&& !detail::is_std_unique_ptr<D>::value // | ||
&& !detail::is_std_shared_ptr<D>::value>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
detail::enable_if_t<detail::type_uses_smart_holder_type_caster<T>::value | |
&& detail::type_uses_smart_holder_type_caster<D>::value | |
&& !std::is_pointer<D>::value // | |
&& !detail::is_std_unique_ptr<D>::value // | |
&& !detail::is_std_shared_ptr<D>::value>> { | |
detail::enable_if_t<detail::all_of< | |
detail::type_uses_smart_holder_type_caster<T>, | |
detail::type_uses_smart_holder_type_caster<D>, | |
!std::is_pointer<D>, // | |
!detail::is_std_unique_ptr<D>, // | |
!detail::is_std_shared_ptr<D>>::value>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing this out! It looks a lot nicer.
using drp = typename std::remove_pointer<D>::type; | ||
|
||
template <typename PM> | ||
static cpp_function readonly(PM pm, const handle &hdl) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static cpp_function readonly(PM pm, const handle &hdl) { | |
static cpp_function readonly(PM &pm, const handle &hdl) { |
and capture by reference in the lambda. At the very least, the PM should be forwarded otherwise.
I think original pybind11 it's a pointer. being passed around.
This could invoke copy or moves depending on the type PMs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't get this to work. I tried a few variations of sprinkling in &
, &&
, std::forward<PM>
, but either it didn't build or I got segfaults. Then I decided it's better to just make it obvious that we're only dealing with pointers anyway, and the perfect forwarding doesn't buy us anything. See the long added comment for the new must_be_member_function_pointer
SFINAE helper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rwgk Made some nits about potentially bugs that could arise from the typing of the templates and that all the helpers could benefit from using the detail::all_of helper defined in details/commons.h
The CI failures are sure to be flakes or (hopefully transient) infrastructure failures. The previous CI run had no failures. I only added a couple comments (and ran |
…he `const_cast` is not needed.
…on pairs struct xetter_cpp_function with read, write.
…just forwards to readonly (to get started).
assert field.num == -99 | ||
with pytest.raises(ValueError) as excinfo: | ||
m.DisownOuter(outer) | ||
assert msg(excinfo.value) == "Cannot disown use_count != 1 (loaded_as_unique_ptr)." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test case is clear and well defined behavior.
My concern is on the practical side -- it is hard to know where the reference to the object is. field could have been passed and kept in many remote part of the application.
A very canonical pattern I know of, for this type of situation is to mark the object as stale, and all uses of it errors. A very typical example is the 'NFS stale handle' error. Then the responsibility is distributed to the consumers of the inner objects, rather than the owner needing to reason a large code base 'who is reference my inner parts?'.
assert msg(excinfo.value) == "Cannot disown use_count != 1 (loaded_as_unique_ptr)." | ||
del field | ||
m.DisownOuter(outer) | ||
with pytest.raises(ValueError) as excinfo: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pytest.raises(ValueError, match=r".* 123 .*")
?
|
||
struct Outer { | ||
// The compact 4-character naming matches that in test_class_sh_basic.cpp | ||
// (c = const, m = mutable). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe comment sh = shared_ptr and uq = unique_ptr as well.
int num = -88; | ||
}; | ||
|
||
struct ClassicOuter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the Classic
prefix imply that this lass in not wrapped in PYBIND11_SMART_HOLDER_TYPE_CASTERS
. A comment above each of these would help with readability.
@@ -0,0 +1,90 @@ | |||
#include "pybind11_tests.h" | |||
|
|||
#include "pybind11/smart_holder.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've seen
#include <pybind11/smart_holder.h>
Elsewhere is there a consistent choice we should make with these includes?
Made some small style comments othwise it looks good to me. |
… as suggested by @charlesbeattie back in Jan/Feb under PR pybind#3581.
I'm continuing this work under PR #3844. See the Note at top of the PR description there. |
… as suggested by @charlesbeattie back in Jan/Feb under PR pybind#3581.
…f PR #3581). (#3844) * Transferred net diff from PR #3581, as-is. * Automatic `pre-commit run --all-files` fixes. NO manual changes. * Removing trailing `//` (originally added to manipulate clang-format), as suggested by @charlesbeattie back in Jan/Feb under PR #3581. * Renaming `xetter_cpp_function` to `property_cpp_function` as suggested by @rainwoodman * Fully explain the terse variable naming scheme in test_class_sh_property (as suggested by @rainwoodman) * Also use parametrize for readonly, readwrite (as suggested by @rainwoodman) * Apply change suggested by @Skylion007 (with clang-format).
Description
NOTE: The continuation of this work is under PR #3844.
The original motivation for this PR was to fix the issue observed here: disowning creates a dangling pointer.
To ensure internal consistency down the road, the scope of this PR was expanded to cover
.def_readonly
and.def_readwrite
behavior forstd::unique_ptr
std::shared_ptr
members (mutable & const for the pointer types).
For
shared_ptr
members, the existing behavior is as desired (no work needed).For value and raw-pointer members, the PyCLIF approach here was adopted: aliasing
shared_ptr
tying the lifetime of the "outer" object to the lifetime of the Python object pointing to the member. In this way the existing mechanisms prevent the "outer" object from being disowned while pointers to members are still alive.For unique_ptr members, the situation is complex. This PR only establishes a proof of concept. See the code and comments in tests/test_class_sh_property.py for details. Productizing the proof of concept is left for a future separate PR, as needed.