classification
Title: Trashcan causing duplicated __del__ calls
Type: Stage: resolved
Components: Interpreter Core Versions: Python 2.7
process
Status: closed Resolution: wont fix
Dependencies: Superseder:
Assigned To: Nosy List: eric.snow, jdemeyer, matrixise, pitrou, scoder, serhiy.storchaka
Priority: normal Keywords: patch

Created on 2019-04-08 10:57 by jdemeyer, last changed 2020-01-10 22:36 by cheryl.sabella. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 12725 closed jdemeyer, 2019-04-08 12:29
Messages (1)
msg339611 - (view) Author: Jeroen Demeyer (jdemeyer) * (Python triager) Date: 2019-04-08 10:57
NOTE: because of PEP 442, this issue is specific to Python 2. This bug was discovered while adding testcases for bpo-35983 to the Python 2.7 backport.

There is a nasty interaction between the trashcan and __del__: if you're very close to the trashcan limit and you're calling __del__, then objects that should have been deallocated in __del__ (in particular, an object involving self) might instead end up in the trashcan. This way, temporary references to self are not cleaned up and self might be resurrected when it shouldn't be. This in turns causes __del__ to be called multiple times.

Testcase:

class ObjectCounter(object):
    count = 0
    def __init__(self):
        type(self).count += 1
    def __del__(self):
        L = [self]
        type(self).count -= 1
L = None
for i in range(60):
    L = [L, ObjectCounter()]
del L
print(ObjectCounter.count)

This is expected to print 0 but in facts it prints -1.

There are various ways of fixing this, with varying effectiveness. An obvious solution is bypassing the trashcan completely in __del__. This will deallocate objects correctly but it will cause a stack overflow (on the C level, so crashing Python) if __del__ is called recursively with deep recursion (this is what the trashcan is supposed to prevent).

A compromise solution would be lowering the trashcan limit for heap types from 50 to 40: this gives __del__ at least 10 stack frames to work with. Assuming that __del__ code is relatively simple and won't create objects that are too deeply nested, this should work correctly.
History
Date User Action Args
2020-01-10 22:36:17cheryl.sabellasetstatus: open -> closed
resolution: wont fix
stage: patch review -> resolved
2019-04-08 12:29:28jdemeyersetkeywords: + patch
stage: patch review
pull_requests: + pull_request12648
2019-04-08 10:57:49jdemeyercreate