Skip to content
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

__del__() order is broken since 3.4.0 #67908

Closed
AlexeyKazantsev mannequin opened this issue Mar 20, 2015 · 14 comments
Closed

__del__() order is broken since 3.4.0 #67908

AlexeyKazantsev mannequin opened this issue Mar 20, 2015 · 14 comments
Assignees
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) performance Performance or resource usage

Comments

@AlexeyKazantsev
Copy link
Mannequin

AlexeyKazantsev mannequin commented Mar 20, 2015

BPO 23720
Nosy @tim-one, @rhettinger, @amauryfa, @pitrou, @vadmium, @serhiy-storchaka
Files
  • bug.py
  • bug2.py
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/pitrou'
    closed_at = None
    created_at = <Date 2015-03-20.12:56:26.520>
    labels = ['interpreter-core', 'invalid', 'performance']
    title = '__del__() order is broken since 3.4.0'
    updated_at = <Date 2015-03-28.10:07:58.120>
    user = 'https://bugs.python.org/AlexeyKazantsev'

    bugs.python.org fields:

    activity = <Date 2015-03-28.10:07:58.120>
    actor = 'terry.reedy'
    assignee = 'pitrou'
    closed = False
    closed_date = None
    closer = None
    components = ['Interpreter Core']
    creation = <Date 2015-03-20.12:56:26.520>
    creator = 'Alexey Kazantsev'
    dependencies = []
    files = ['38599', '38621']
    hgrepos = []
    issue_num = 23720
    keywords = []
    message_count = 13.0
    messages = ['238661', '238662', '238669', '238680', '238787', '238788', '238790', '238791', '238792', '238794', '238796', '238829', '239437']
    nosy_count = 8.0
    nosy_names = ['tim.peters', 'rhettinger', 'amaury.forgeotdarc', 'pitrou', 'martin.panter', 'serhiy.storchaka', 'Vadim Markovtsev', 'Alexey Kazantsev']
    pr_nums = []
    priority = 'low'
    resolution = 'not a bug'
    stage = 'needs patch'
    status = 'open'
    superseder = None
    type = 'performance'
    url = 'https://bugs.python.org/issue23720'
    versions = ['Python 3.5']

    @AlexeyKazantsev
    Copy link
    Mannequin Author

    AlexeyKazantsev mannequin commented Mar 20, 2015

    Pythons prior to 3.4.0 print

    Vector!
    Device!

    while >=3.4.0 print

    Device!
    Vector!

    If we replace Main with Vector on line 21, the behavior becomes random: in 50% of all cases it prints the wrong sequence, in other 50% the right. Our team treats this as a bug for several reasons:

    1. Objects should be destroyed in breadth first reference tree traversal order, starting from the root. There are no cycles. It is nonsense to have freed children in parent's destructor.

    2. Our applications suffer very much from this bug. Real "Vector" holds GPGPU memory and real "Device" holds the context, and CUDA/OpenCL require the context to be freed the last. With CUDA, the invalid destructor call order leads to segmentation faults.

    This may have something to deal with the implementation of PEP-442 (though in our case there no reference cycles at all).

    @AlexeyKazantsev AlexeyKazantsev mannequin added type-bug An unexpected behavior, bug, or error interpreter-core (Objects, Python, Grammar, and Parser dirs) labels Mar 20, 2015
    @VadimMarkovtsev
    Copy link
    Mannequin

    VadimMarkovtsev mannequin commented Mar 20, 2015

    +1

    @amauryfa
    Copy link
    Member

    Actually there *is* a cycle:
    assert a.vector is a.vector.device.__class__.__del__.__globals__['a'].vector

    A workaround is to not store objects with __del__ in module globals...
    Or have only one (the Main instance in your case)

    @pitrou
    Copy link
    Member

    pitrou commented Mar 20, 2015

    Amaury is right. In your case you could keep track of the Vectors in the Device object and invalidate them when the Device is destroyed (using e.g. a WeakSet). Or Vector could delegate its destruction to Device, e.g.:

    class Device(object):        
        destroyed = False
    
        def __del__(self):
            self.destroyed = True
    
        def _dealloc_vector(self, v):
            if not self.destroyed:
                ...
    
    
    class Vector(object):        
        def __init__(self, device):
            self.device = device
            
        def __del__(self):
            self.device._dealloc_vector(self)

    @pitrou pitrou closed this as completed Mar 20, 2015
    @AlexeyKazantsev
    Copy link
    Mannequin Author

    AlexeyKazantsev mannequin commented Mar 21, 2015

    Ok, even assuming that all module globals are in circular reference starting with python 3.4, here is another example without using the globals:

    Brief description:
    v holds reference to d
    a.v = v
    b.d = d
    Now when we form a circular reference a <-> b, the destructor order becomes wrong for v and d.

    @AlexeyKazantsev AlexeyKazantsev mannequin reopened this Mar 21, 2015
    @serhiy-storchaka
    Copy link
    Member

    There is a cycle for every class with a method.

    >>> class A:
    ...     def __del__(self): pass
    ... 
    >>> A.__del__.__globals__['A']
    <class '__main__.A'>

    @vadmium
    Copy link
    Member

    vadmium commented Mar 21, 2015

    There is a cycle involving the class object, but I don’t think there is a cycle involving the instance objects this time.

    However, I wonder if __del__() is meant to be called in any particular order anyway. What’s to stop the garbage collector itself from creating a temporary reference to the Device instance, destroying the Vector instance, which invokes Vector.__del__(), and finally destroying the temporary reference to the Device instance?

    @pitrou
    Copy link
    Member

    pitrou commented Mar 21, 2015

    Alexey, you're right that in this case (bug2.py) the cyclic GC is a bit less friendly than it was. It's not obvious there's a way to change that without introduce a lot of complexity. I'll try to take a look some day, although others may help too :-)

    That said, my advice in msg238680 still holds. When you're writing a Python wrapper around a C or C++ library with well-defined ownership relationships, I think you should enforce those in the Python wrapper as well (that's my experience with llvmlite, anyway).

    @serhiy-storchaka
    Copy link
    Member

    There is a cycle involving the class object, but I don’t think there is a cycle involving the instance objects this time.

    It is.

    >>> a = A()
    >>> a.__class__.__del__.__globals__['a']
    <__main__.A object at 0xb702230c>

    And all objects referenced from user class or module level instance of user class are in a cycle. This problem can cause issues such as bpo-17852.

    @vadmium
    Copy link
    Member

    vadmium commented Mar 21, 2015

    But in the case of bug2.py, “a” is a variable inside main(), not a global variable.

    BTW I take back my second paragraph questioning the whole order thing; I clearly didn’t think that one through.

    @serhiy-storchaka
    Copy link
    Member

    Yes, in bug2.py we have different cycle.

    a ↔ b
    ↓ ↓
    v → d

    a and b are in a cycle, and therefore v and d are in cycle. I think that in such case v always should be destroyed before d, independently of a cycle that refers them. And this is the same situation, as for io classes. A TextIOWrapper object refers a BufferedWriter object, a BufferedWriter object refers a FileIO object. and some cycle refers a TextIOWrapper object. As a result a FileIO object can be closed before a TextIOWrapper object or a BufferedWriter object flush its buffer.

    @rhettinger
    Copy link
    Contributor

    Can this be closed as not-a-bug.

    @terryjreedy
    Copy link
    Member

    While this is not a bug, Antoine said he might look at improving the situation, so I leave it to him to close it or not.

    @terryjreedy terryjreedy added performance Performance or resource usage and removed type-bug An unexpected behavior, bug, or error labels Mar 27, 2015
    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    @AlexWaygood
    Copy link
    Member

    There seems to be agreement that this is not a bug, and there's been no activity on this issue for 7 years. I'm therefore closing the issue. If any of the participants are still interested in working on this problem, they're obviously free to reopen the issue.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    interpreter-core (Objects, Python, Grammar, and Parser dirs) performance Performance or resource usage
    Projects
    None yet
    Development

    No branches or pull requests

    7 participants