-
Notifications
You must be signed in to change notification settings - Fork 83
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
Systematically replace __del__
with weakref.finalize()
#246
Conversation
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, Ralf! A general comment: We can't use self.close
as the finalizer because it'd still hold a reference to self (see @wence-'s original #87 (comment)). The most simple thing we could do is to move it out of the class, so instead of
class Something:
def __init__(self, ...):
self._handle = ...
weakref.finalize(self, self.close)
def close(self, ...):
if self._handle:
# do something
we do
# note the 1st arg name is still "self", to mimic a method
def _close_something(self, ...):
if self._handle:
# do something
class Something:
def __init__(self, ...):
self._handle = ...
weakref.finalize(self, _close_something)
Ah, confirmed, by looking more. Yesterday I was jumping to a wrong conclusion, twisty explanation omitted. I'll try again. |
I think I found a nice pattern to avoid reference cycles. This is my updated toy example: The trick is to introduce a level of indirection, I still need to adopt that pattern in this PR. |
Corresponding demonstration of finalize behavior (immediate cleanup): https://github.com/rwgk/stuff/blob/f6fbd670b8376003c7767c96538d8ab0b1f49d96/random_attic/weakref_finalize_toy_example.py
Thanks, Ralf! I might be missing some context here, but I'm not sure how to interpret the output of the |
Spoke with Ralf offline and now I have more clarity. Some notes from our call:
|
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 think this is a clever approach around the limitation of weakref.finalize
not being able to accept a bound method as a finalizer.
Approving with one caveat/question. Why don't we just handle the destruction of stream, event, etc., in the Cython, i.e.,:
cdef class CUStream:
def __cinit__(self, ...):
# presumably we have a __cinit__
def __dealloc__(self):
# why don't we just call cuStreamDestroy in a __dealloc__?
That didn't cross my mind, sounds interesting, but I don't know enough Cython to know if that'll work. @leofang I'd be happy to play with that, as one way for me to get deeper into Cython, if you think that makes sense. |
If the subject is the low-level
If the subject is the pythonic
|
We could have both:
|
Right, it'd be a refactoring to what's done in this PR. I am fine with pursing this at a later time (this PR looks fine to me and gets the job done), but if we lower the entire |
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.
Q: @rwgk IIRC you mentioned finalizer.detach()
is needed if close()
is explicitly called (to avoid double free). I assume it's no longer true because the finalizer becomes a no-op due to the pre-conditions such as if self.handle is not None
?
Yes, the way I think about it: By way of the intermediate object ( |
/ok to test |
Closes #141
See #141 (comment) for the rationale.
This
_MembersNeededForFinalize
pattern is applied systematically in cuda_core:The weakref_finalize_toy_example.py demonstrates conclusively that the finalizer runs immediately when the main object (
ShopKeeper
in the example) goes out of scope; i.e. the_MembersNeededForFinalize
pattern does not create reference cycles:For simple cases (e.g. _event.py) the
_MembersNeededForFinalize
pattern might seem more complex than necessary, but note that it is generally safer. See commit 26ddbf6 for a side-by-side comparison. Note thatself._finalizer.Detach()
is needed in the slightly simpler alternative. The need for this is likely to be overlooked when the code is extended or refactored in the future and could lead to situations akin to a double-free that may only be discovered in production.