-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Failing test for std::weak_ptr
used with derived Python class and py::smart_holder
#5624
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
base: master
Are you sure you want to change the base?
Conversation
virtual ~WpBase() = default; | ||
}; | ||
|
||
struct PyWpBase : WpBase { |
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.
struct PyWpBase : WpBase, py::trampoline_self_life_support {
Does that help?
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 just fixed an oversight in my previous comment.)
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.
struct PyWpBase : WpBase { | |
struct PyWpBase : WpBase, py::trampoline_self_life_support { |
I simplified the reproducer and changed the variable names to make them more intuitive (to me). I'm guessing we're building an intermediate I wrote that code four years ago. Quite likely this will take me several hours to re-learn what exactly is happening. Not sure when I'll get to it. @nsoblath How did you arrive at that test? Did you have code that worked with |
…s holder). Tweak test_class_sh_trampoline_weak_ptr.py to pass, with `# THIS NEEDS FIXING` comment.
On that suspicion, I added test_class_sp_trampoline_weak_ptr.cpp,py, and it seems to work: it certainly works locally, but I'm waiting for GitHub Actions to finish. Assuming the new test works reliably on all platforms, I'd guess that it's feasible to fix the smart_holder equivalent. |
``` /__w/pybind11/pybind11/tests/test_class_sh_trampoline_weak_ptr.cpp:23:43: error: the parameter 'sp' is copied for each invocation but only used as a const reference; consider making it a const reference [performance-unnecessary-value-param,-warnings-as-errors] 23 | void set_wp(std::shared_ptr<VirtBase> sp) { wp = sp; } | ^ | const & 4443 warnings generated. ``` ``` /__w/pybind11/pybind11/tests/test_class_sp_trampoline_weak_ptr.cpp:23:43: error: the parameter 'sp' is copied for each invocation but only used as a const reference; consider making it a const reference [performance-unnecessary-value-param,-warnings-as-errors] 23 | void set_wp(std::shared_ptr<VirtBase> sp) { wp = sp; } | ^ | const & 4430 warnings generated. ```
Before I wrote:
That's here (conclusive):
Note that the
So in line 782 we need to return a
Any ideas, please let me know. (I haven't thought about it much yet; the penny didn't want to drop immediately.) @virtuald for visibility |
I copied the test_class_sh_trampoline_shared_ptr_cpp_arg test, modified it to store a |
I've now turned this over in my mind for a couple hours, and consulted ChatGPT, which I believe got very confused/confusing, but please see for yourself here. Stepping out of the box:
Solving this is a real brain teaser. I'm not sure I can help. I'm not sure if there is a viable solution. I have a couple ideas, but each of them might take hours of work only to find out it leads to a dead end. Could you try to work around this limitation? For example, instead of owning the C++ |
…s does not inherit from this class, preserve established Inheritance Slicing behavior. rwgk reached this point with the help of ChatGPT: * https://chatgpt.com/share/68056498-7d94-8008-8ff0-232e2aba451c The only production code change in this commit is: ``` diff --git a/include/pybind11/detail/type_caster_base.h b/include/pybind11/detail/type_caster_base.h index d4f9a41e..f3d45301 100644 --- a/include/pybind11/detail/type_caster_base.h +++ b/include/pybind11/detail/type_caster_base.h @@ -776,6 +776,14 @@ struct load_helper : value_and_holder_helper { if (released_ptr) { return std::shared_ptr<T>(released_ptr, type_raw_ptr); } + auto *self_life_support + = dynamic_raw_ptr_cast_if_possible<trampoline_self_life_support>(type_raw_ptr); + if (self_life_support == nullptr) { + std::shared_ptr<void> void_shd_ptr = hld.template as_shared_ptr<void>(); + std::shared_ptr<T> to_be_released(void_shd_ptr, type_raw_ptr); + vptr_gd_ptr->released_ptr = to_be_released; + return to_be_released; + } std::shared_ptr<T> to_be_released( type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst)); vptr_gd_ptr->released_ptr = to_be_released; ```
I'm testing commit 4638e01 to see if that works on all platforms. See https://chatgpt.com/share/68056498-7d94-8008-8ff0-232e2aba451c for how I got there / rationale. I'm not sure if 4638e01 is a wise twist to add. Fundamentally, it'd be better to support |
``` 11>D:\a\pybind11\pybind11\tests\test_class_sp_trampoline_weak_ptr.cpp(44,50): error C2220: the following warning is treated as an error [D:\a\pybind11\pybind11\build\tests\pybind11_tests.vcxproj] 11>D:\a\pybind11\pybind11\tests\test_class_sp_trampoline_weak_ptr.cpp(44,50): warning C4458: declaration of 'sp' hides class member [D:\a\pybind11\pybind11\build\tests\pybind11_tests.vcxproj] D:\a\pybind11\pybind11\tests\test_class_sp_trampoline_weak_ptr.cpp(54,31): see declaration of 'pybind11_tests::class_sp_trampoline_weak_ptr::SpOwner::sp' ```
@nsoblath I feel I'm pretty clear now about the situation. I think it's best not to change the smart_holder code, but to go with the approach outlined before:
I'll explain more when I get to it, probably next weekend. (The only thing I want to change, probably, is to add a |
@rwgk Thank you very much for your time and attention looking into this. I really appreciate it. Though my understanding of the internals of pybind11 is quite incomplete, my thoughts about what might be going wrong were similar to what you suggested: that the control block for the In your later message you said
Does that mean you're preferring to not use the change in 4638e01? Is there a downside to making sure that the control block of the returned Regarding the alternative solution you proposed:
I believe I can implement something to this effect. It's not ideal because the library where I'm using this is sometimes used as a pure-C++ library, sometimes used with its Python bindings. |
Yes.
Yes, it'll be very confusing to many (I think), while only being useful in a few niche cases. It is not obvious at all that the I feel it'll be better to keep the smart_holder feature simpler and the behavior consistent. It will be easier to reason about the bindings. We could probably add an interface that makes it relatively easy for you to get the inheritance-slicing- What I didn't explain: I convinced myself that it is impossible to get a
Reason: This would introduce a hard reference cycle, because the Python object ( |
Description
PR is in support of issue #5623.
The added test shows that if you hold a reference to a derived Python object with a C++
std::weak_ptr
, and the relevant classes usepy::smart_holder
, theweak_ptr
will expire even if the Python object still exists.