Uh oh!
There was an error while loading. Please reload this page.
fix: test_factory_constructors.py failure triggered by test_register_duplicate_class - #2564
Conversation
I wonder if this is related to #2558 ? Edit: Sadly it didn't fix this one. |
YannickJadoul
commented
Oct 8, 2020
It would be amazing if it is (and we only need to solve 1 issue instead of 2), but I wouldn't really know why it would be? |
henryiii
commented
Oct 8, 2020
https://github.com/pybind/pybind11/pull/2564/checks?check_run_id=1227843279 looks suspicious to me. Notice the second test that failed (though not a segfault). |
3752dd6 to
d8eb801Comparewjakob
commented
Oct 12, 2020
That's a nice catch. One word of caution about |
Hmmm, interesting. I'll still give that a look! I did think of this, but it seemed reasonably deterministic, the way it's described in the Python docs (both normal as well as C API):
So I understood it's not linked to the garbage collector running on the weakref, but on the reference object? (Which is fine, because as long as that one's not collected, Python won't recycle the memory anyway.) But I'll look into #856 and try to convince myself that we're good! |
YannickJadoul
commented
Oct 12, 2020
Scratch all that. Should've read the issue first; this is not really related. @wjakob, @henryiii, @EricCousineau-TRI, I still think we should be safe, because we're not too concerned about order, here? Worst case that could happen is some type gets removed from the internals, a bit before that type is actually destructed, but áfter the last reference to this type is gone? (Except if there's some weird cycle, perhaps, and the type is still used in the destructor? But I'm not sure we could fix that anyway, given the arbitrary destruction order of a cycle?) We could try a similar approach to #856; I thought about that before seeing the |
henryiii
commented
Oct 12, 2020
I think this is fine, as it fixes the issue we are seeing, though if something else comes up, we can revisit? |
YannickJadoul
commented
Oct 12, 2020
Let's give @EricCousineau-TRI still a chance to review? He said he might have some time tomorrow. And I have a feeling he's been playing in these parts of pybind11 before. |
This is in pybind11/include/pybind11/detail/internals.h Lines 97 to 109 in 7c71dd3 What else should we clear out? EDIT(eric): Made it a permalink code ref. |
YannickJadoul
commented
Oct 12, 2020
Thinking a bit further: is that the behavior we actually want? I.e., in this case, once the Python type object (the one of the class_) gets garbage collected, it's unregistered. So it would also mean you can't go from C++ to Python, anymore. So you couldn't register an "anonymous" type with Or, you register a C++ type (To be clear, the above scenario currently results in a segfault, so at least it's better. But the other option would be to keep C++-registered type alive forever.) |
henryiii
commented
Oct 13, 2020
I think this is good for now, as it's at least better, and I don't think deleting class objects is common (except in some tests). And if you go to the trouble of deleting all references to a class, would you expect it to magically reappear if you tried to do a conversion afterwords? |
Agreed with Henry. If a user manages to clear out references, then they probably have a reason to do so, so we shouldn't get in the way. Additionally, a user could prevent that behavior by stashing a reference to the module (or type), preventing garbage colllection: This is effectively what happens in nominal Python anyways. |
EricCousineau-TRI
left a comment
There was a problem hiding this comment.
Looks good! Just some minor comments.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
YannickJadoul
commented
Oct 13, 2020
True, OK. I was just afraid of unexpected surprises where suddenly a functions return type can't be returned anymore. In Python you'd never have that, because there'd still be a reference to it, when creating the object. But I'm fine with waiting for complaints :-) |
wjakob
commented
Oct 15, 2020
After taking another look, I am now convinced that the weak reference will work. That said, it seems far too heavy to create This would imply that the implementation has to be sufficiently generic -- it must e.g. map from the type to |
That's where I got this approach, yes. I got reasonably confused for a while on whý things weren't getting cleaned up. It is used here, though, I think: const std::vector<detail::type_info *> &all_type_info(PyTypeObject *type) {
auto ins = all_type_info_get_cache(type);
if (ins.second)
// New cache entry: populate itall_type_info_populate(type, ins.first->second);
return ins.first->second;
} |
wjakob
commented
Oct 15, 2020
That is an excellent point, I had forgotten about that. Then how about catching things at a lower level: https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_finalize, in this case in the metaclass used to construct pybind11 types? |
YannickJadoul
commented
Oct 15, 2020
That is the other option, yes. The detail here is that using a custom metaclass through |
YannickJadoul
commented
Oct 15, 2020
By which I mean: going with |
YannickJadoul
commented
Oct 15, 2020
|
…tional changes from pybind#2335" This reverts commit ca33a80.
cfada0c to
51978a8Compare2b8a3f4 to
ae96b37CompareYannickJadoul
commented
Oct 15, 2020
YannickJadoul
commented
Oct 15, 2020
@rwgk I fixed the |
henryiii
commented
Oct 16, 2020
I take it the second approach breaks custom meta classes that don't also call this? |
YannickJadoul
commented
Oct 16, 2020
Yes. Then again, they'd only be as broken as the current situation (which has been there for a long time, if not forever, as far as I know?). |
bstaletic
commented
Oct 16, 2020
I was going to ask if custom metaclasses ever worked in combination with pybind11. |
YannickJadoul
commented
Oct 16, 2020
Kind of (there is at least the |
rwgk
left a comment
There was a problem hiding this comment.
This PR passed the Google-global testing.
henryiii
commented
Oct 16, 2020
Okay, either approach is fine with me then. |
wjakob
commented
Oct 16, 2020
Beautiful, thank you for the great work @YannickJadoul. I think that we can likely install a similar callback also for custom metaclasses, but this a much less important point that is not a blocker for v.2.6.0 (this kind issue revolving around garbage collection of pybind11-created types is rare enough in practice that we are just bothered by it now, and custom metaclasses are a super-rarely used feature of pybind11). Pr{A ∩ B}≈ Pr{A}·Pr{B}=tiny number, I hope 🙂. |
To reproduce, this fails on the
centos:8docker image (with development tools installed - seeci.yml- but without per se installing nvidia compilers), by runningPYTEST_ADDOPTS="-k 'test_register_duplicate_class or test_init_factory_alias'" cmake --build build_centos8 --target pytestCross-reference #2335
EDIT: Figured out what's wrong. Short description of the bug:
test_class.py::test_register_duplicate_classregisterspy::class_instances in different scopes, a module and a class (to test whether duplicate class names or types are caught; but that's not really the issue here).py::class_constructor are not referenced anymore after the scope of the test, and they go out of scope and get garbage collected. Since thepy::class_es are only referenced by these scopes that are getting garbage collected, they also get garbage collected. However, they remain in the internals'registered_types_py.PyTypeObjectpointer inregistered_types_pynow points to a new Python type, but still associates this with the oldtype_info(it is likely important that the new type allocated in "recycled" memory is a non-pybind11 type, in the case I debugged; if not, the associatedtype_infoinregistered_types_pywould be overwritten).PyTypeObject*inregistered_types_py.Current solution:
weakreffromdetail::all_type_info_get_cache. I still want to check if there's a better way of solving this, but this solves the problem.Possible follow-up:
class_. I'll give this some more thought and I'll make a PR to further discuss this.