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

Infinite recursion while garbage collecting loops indefinitely #55003

Closed
dizzy mannequin opened this issue Dec 30, 2010 · 7 comments
Closed

Infinite recursion while garbage collecting loops indefinitely #55003

dizzy mannequin opened this issue Dec 30, 2010 · 7 comments
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error

Comments

@dizzy
Copy link
Mannequin

dizzy mannequin commented Dec 30, 2010

BPO 10794
Nosy @gpshead, @amauryfa, @iritkatriel
Files
  • unnamed
  • unnamed
  • 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 = None
    closed_at = <Date 2021-04-16.22:44:16.284>
    created_at = <Date 2010-12-30.00:51:25.690>
    labels = ['interpreter-core', 'type-bug']
    title = 'Infinite recursion while garbage collecting loops indefinitely'
    updated_at = <Date 2021-04-16.22:44:16.284>
    user = 'https://bugs.python.org/dizzy'

    bugs.python.org fields:

    activity = <Date 2021-04-16.22:44:16.284>
    actor = 'iritkatriel'
    assignee = 'none'
    closed = True
    closed_date = <Date 2021-04-16.22:44:16.284>
    closer = 'iritkatriel'
    components = ['Interpreter Core']
    creation = <Date 2010-12-30.00:51:25.690>
    creator = 'dizzy'
    dependencies = []
    files = ['20196', '20208']
    hgrepos = []
    issue_num = 10794
    keywords = []
    message_count = 7.0
    messages = ['124896', '124915', '124916', '124921', '124984', '124986', '386891']
    nosy_count = 4.0
    nosy_names = ['gregory.p.smith', 'amaury.forgeotdarc', 'dizzy', 'iritkatriel']
    pr_nums = []
    priority = 'normal'
    resolution = 'out of date'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue10794'
    versions = ['Python 2.7']

    @dizzy
    Copy link
    Mannequin Author

    dizzy mannequin commented Dec 30, 2010

    Hi

    While working on some Python code I stumbled on a situation where the Python process seems to hang indefinitely. Further debugging points to the following conclusion: if there is a class that somehow manages to run into an infinite recursion (properly detected by Python which raises the corresponding RuntimeError) while Python is doing garbage collection (so the infinite recursion has to be triggered from __del__ somehow) then Python hangs into an infinite loop apparently retrying to call that __del__ indefinitely. The described behavior happens ONLY when an infinite recursion is detected (ie if I raise my own "RuntimeError" from __del__ then it just logs it and exits).

    Program showing the problem:

    class A(object):
      def __init__(self):
        raise Exception('init error')
        self.m = 'Hello world'
    
      def __del__(self):
        #raise RuntimeError('my runtime error')
        self.__del__()
    
    def func():
      h = A()
    
    func()
    $ python pyloop.py
    Traceback (most recent call last):
      File "pyloop.py", line 15, in <module>
        func()
      File "pyloop.py", line 13, in func
        h = A()
      File "pyloop.py", line 5, in __init__
        raise Exception('init error')
    Exception: init error
    Exception RuntimeError: 'maximum recursion depth exceeded' in <bound method A.__del__ of <__main__.A object at 0x17bb7d0>> ignored
    Exception RuntimeError: 'maximum recursion depth exceeded' in <bound method A.__del__ of <__main__.A object at 0x17bb7d0>> ignored
    Exception RuntimeError: 'maximum recursion depth exceeded' in <bound method A.__del__ of <__main__.A object at 0x17bb7d0>> ignored
    Exception RuntimeError: 'maximum recursion depth exceeded' in <bound method A.__del__ of <__main__.A object at 0x17bb7d0>> ignored
    ...

    If I uncomment the line raising my RuntimeError instance from __del__ then the exception is logged once and the program exits (as expected).

    Please help, thanks.

    @dizzy dizzy mannequin added interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error labels Dec 30, 2010
    @amauryfa
    Copy link
    Member

    Normally you should never call __del__, OTOH the issue is the same with a class like::

    class A:
        def close(self):
            self.close()
        def __del__(self):
            self.close()

    The problem is not with _infinite_ recursion, though; a depth of 47 is enough (but 46 won't show the problem)::

    class A:
        def __del__(self):
            self.recurse(47)
        def recurse(self, n):
            if n:
                self.recurse(n-1)
            else:
                raise ValueError

    Hint: PyTrash_UNWIND_LEVEL is defined to 50; I checked that recompiling Python with a different value also changes the necessary recursion depth.

    There is some nasty interaction between the "Trashcan mechanism" and resurrected objects stored in deep structures. A list is enough, no need to involve frames and exceptions::

    class C:
        def __del__(self):
            print ".",
            x = self
            for i in range(49):    # PyTrash_UNWIND_LEVEL-1
                x = [x]
    l = [C()]
    del l

    @amauryfa
    Copy link
    Member

    Precision: with new-styles classes (or py3k) the limit is PyTrash_UNWIND_LEVEL-2. This does not change anything to the problem.

    @gpshead
    Copy link
    Member

    gpshead commented Dec 30, 2010

    FWIW, the example pasted in the bug was the smallest one he could come up
    with. in reality we were never calling .__del__() explicitly. We ran into
    the problem due to a __del__ method triggering a __getattr__ call and the
    __getattr__ ending up in infinite recursion because an attribute it accessed
    internally wasn't defined. That particular bug was fixable by fixing the
    __getattr__ to not depend on any instance attributes but it is still a
    problem in Python for the interpreter to get into an infinite loop calling
    the destructor in that case...

    @terryjreedy
    Copy link
    Member

    2.6 is finished except for possible security patches.
    This should be verified in a current release, preferably 3.2

    @gpshead
    Copy link
    Member

    gpshead commented Jan 1, 2011

    it happens on 3.2 (py3k head).

    @iritkatriel
    Copy link
    Member

    I think this issue is out of date.

    For Mihai's example I get:

    >>> class A(object):
    ...   def __init__(self):
    ...     raise Exception('init error')
    ...     self.m = 'Hello world'
    ...   def __del__(self):
    ...     #raise RuntimeError('my runtime error')
    ...     self.__del__()
    ...
    >>> def func():
    ...   h = A()
    ...
    >>> func()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 2, in func
      File "<stdin>", line 3, in __init__
    Exception: init error
    >>>

    For Amaury's first example I get what I expect:

    >>> class A:
    ...     def close(self):
    ...         self.close()
    ...     def __del__(self):
    ...         self.close()
    ...
    >>> class A:
    ...     def close(self):
    ...         self.close()
    ...     def __del__(self):
    ...         self.close()
    ...
    >>> def func():
    ...   h = A()
    ...
    >>> func()
    Exception ignored in: <function A.__del__ at 0x000002921A8E1820>
    Traceback (most recent call last):
      File "<stdin>", line 5, in __del__
      File "<stdin>", line 3, in close
      File "<stdin>", line 3, in close
      File "<stdin>", line 3, in close
      [Previous line repeated 994 more times]
    RecursionError: maximum recursion depth exceeded
    >>>

    And for Amaury's trashcan example (even when I increase the list length to well over the trashcan threshold):

    >>> class C:
    ...     def __del__(self):
    ...         print('.')
    ...         x = self
    ...         for i in range(49):    # PyTrash_UNWIND_LEVEL-1
    ...             x = [x]
    ...
    >>> l = [C()]
    >>> del l
    .
    >>> class C:
    ...      def __del__(self):
    ...          print('.')
    ...          x = self
    ...          for i in range(5000):
    ...             x = [x]
    ...
    >>> l = [C()]
    >>> del l
    .
    >>>

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    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) type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    4 participants