py: __del__ special method not implemented for user-defined classes
Example:
import gc
class Foo():
def __del__(self):
print('__del__')
f = Foo()
del f
gc.collect()
According to this post #1802, gc.collect() should collect the object after the del and call the finaliser, but the finaliser is never called, any ideas?
I know that a good programming practice is to assume that __del__ may never be called, but hey, this still should work :)
py/objtype: Implement __del__ data model method for user classes.
Summary
This PR implements the required functionality to allow the garbage collector to run user-defined __del__ methods as finalisers.
Testing
I've added full-coverage tests of this feature that I've tested for sensitivity across both the Unix port and my RP2040 and RP2350 boards. All tests pass in all the environments I've tested it in.
Note that the test is specifically designed to tolerate an expected number of failures, due to the fact that the GC is conservative and doesn't make much in the way of guarantees. Out of the 100 create/delete cycles in its checking phase, it allows up to 2 of their finalisers to be missed run. I've only observed cases where a single object's finaliser was missed, so I've set this threshold as 1 higher than that to ensure the test doesn't become another low-selectivity thorn in the CI.
Trade-offs and Alternatives
This PR claims two additional bits from the mp_obj_type_t.flags field, for MP_TYPE_FLAG_IS_INSTANCED and MP_TYPE_FLAG_HAS_FINALISER. The latter bit is required in order to track whether instances should be allocated using the _with_finaliser allocators, while the former is required to track whether it's still safe to allow the __del__ method to be inserted into a class after-the-fact. It's possible that MP_TYPE_FLAG_IS_INSTANCED could be eliminated by forbidding this type of insertion entirely.
With the addition of the MP_TYPE_FLAG_HAS_FINALISER bit, it might be possible to make use of this flag in the finaliser lookup code, as a cheaper way to fail out than a lookup miss; but this would be redundant with the FTB table check.
As another alternative, this could be re-implemented to make the finaliser into an object slot, though this would make all type objects larger.
References
Fixes: #1878
PEP 442 – Safe object finalization
See also:
- #1878
- #646
- #2621
- #17142