Improve binding removal logic#5
Conversation
adcc6e9 to
dad39eb
Compare
The previous technique of requiring `pymb_remove_binding()` to be called when the type object has zero references turns out to not be tenable for binding types that don't control their metaclass. The best one can do for such types is a weakref, and GC executes weakref callbacks before breaking cycles. Create new semantics for binding removal that take care of removal automatically when either the type object is being finalized or the binding capsule is destroyed, whichever comes first. These rely on the fact that free-threaded builds use deferred refcounting for type objects, so it's only possible for a type object to be deallocated during GC. Since free-threaded GC contains a stop-the-world pause in between running finalizers/weakrefs and breaking references, we can protect concurrent binding users by unpublishing the binding during type finalization but not deallocating it until type destruction. If the binding is to be removed before its type dies, we use a capsule-in-a-cycle to defer the deallocation until the next GC pass.
dad39eb to
8685a2e
Compare
|
Apart from accounting for this towards #4 , tell me if you would like me to take a look at this PR but one thing I noticed skimming at the approach (which may not be a problem but is still good to have present) is that technically speaking weakreaf callbacks may not be honoured in some situations. The most representative one is if they are involved in the transitive closure of unreachable cycles with the object they refer. For example: this prints: You can check more about it here: https://github.com/python/cpython/blob/4afa98596ea0182a9aed1556866813052c425a8e/Python/gc.c#L957-L961 Of course this may not apply to your current design, but if the weakref is exposed somehow or is under user management at some point that may be a potential trouble. |
Thanks for pointing this out. Since the weakrefs I'm using here are kept alive by references that are not visible to GC, I don't think this is a hazard for the current implementation. |
I think it's very unlikely we will change this (and I am sure I am not pointing out something you haven't already considered) but it's technically possible we change this in the future (or even between patch versions) so this has a nonzero chance of becoming a bit brittle in the future. |
4bd9196 to
5affcd1
Compare
The previous technique of requiring
pymb_remove_binding()to be called when the type object has zero references turns out to not be tenable for binding types that don't control their metaclass. The best one can do for such types is a weakref, and GC executes weakref callbacks before breaking cycles.Create new semantics for binding removal that take care of removal automatically when either the type object is being finalized or the binding capsule is destroyed, whichever comes first. These rely on the fact that free-threaded builds use deferred refcounting for type objects, so it's only possible for a type object to be deallocated during GC. Since free-threaded GC contains a stop-the-world pause in between running finalizers/weakrefs and breaking references, we can protect concurrent binding users by unpublishing the binding during type finalization but not deallocating it until type destruction. If the binding is to be removed before its type dies, we use a capsule-in-a-cycle to defer the deallocation until the next GC pass.