Skip to content

test_type_comments leaks references #80060

Closed

Description

@vstinner
BPO 35879
Nosy @gvanrossum, @ambv, @pablogsal
PRs
  • bpo-35879: Fix type comment leaks #11728
  • bpo-35879: Fix type comment leaks #11728
  • bpo-35879: Fix type comment leaks #11728
  • 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/gvanrossum'
    closed_at = <Date 2019-02-01.23:31:43.777>
    created_at = <Date 2019-02-01.11:30:04.496>
    labels = ['3.8', 'type-bug', 'library']
    title = 'test_type_comments leaks references'
    updated_at = <Date 2019-02-01.23:31:43.777>
    user = 'https://github.com/vstinner'

    bugs.python.org fields:

    activity = <Date 2019-02-01.23:31:43.777>
    actor = 'gvanrossum'
    assignee = 'gvanrossum'
    closed = True
    closed_date = <Date 2019-02-01.23:31:43.777>
    closer = 'gvanrossum'
    components = ['Library (Lib)']
    creation = <Date 2019-02-01.11:30:04.496>
    creator = 'vstinner'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 35879
    keywords = ['patch', 'patch', 'patch']
    message_count = 13.0
    messages = ['334671', '334682', '334702', '334708', '334709', '334713', '334717', '334718', '334721', '334722', '334723', '334730', '334731']
    nosy_count = 3.0
    nosy_names = ['gvanrossum', 'lukasz.langa', 'pablogsal']
    pr_nums = ['11728', '11728', '11728']
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue35879'
    versions = ['Python 3.8']

    Activity

    1. vstinner commented on Feb 1, 2019

      @vstinner
      MemberAuthor

      https://buildbot.python.org/all/#/builders/1/builds/490

      https://buildbot.python.org/all/#/builders/80/builds/499

      test_type_comments leaked [37, 37, 37] references, sum=111
      test_type_comments leaked [11, 12, 11] memory blocks, sum=34

      Example:

      vstinner@apu$ ./python -m test -R 3:3 test_type_comments -m test.test_type_comments.TypeCommentTests.test_asyncdef
      Run tests sequentially
      0:00:00 load avg: 0.56 [1/1] test_type_comments
      beginning 6 repetitions
      123456
      ......
      test_type_comments leaked [2, 2, 2] references, sum=6
      test_type_comments leaked [2, 3, 2] memory blocks, sum=7
      test_type_comments failed

      == Tests result: FAILURE ==

      1 test failed:
      test_type_comments

      Total duration: 129 ms
      Tests result: FAILURE

      I bet that the regression comes from:

      commit dcfcd14
      Author: Guido van Rossum <[email protected]>
      Date: Thu Jan 31 03:40:27 2019 -0800

      bpo-35766: Merge typed_ast back into CPython (GH-11645)
      

      See also bpo-35878.

    2. added
      stdlibStandard Library Python modules in the Lib/ directory
      on Feb 1, 2019
    3. pablogsal commented on Feb 1, 2019

      @pablogsal
      Member

      From the duplicate (https://bugs.python.org/issue35881) :

      After some hours of debugging, I have identified the (possible) cause of the majority of the refleaks.

      The type_comments created in ast.c with new_type_comment() never reaches 0 reference counts. This **seems** to be because the cleanup for the stmt_ty elements where the type_comments are included never call DECREF on these elements when destroyed unless I am missing something fundamental.

    4. gvanrossum commented on Feb 1, 2019

      @gvanrossum
      Member

      I can confirm it, but I don't understand it yet. Somehow the type_comment fields get an extra reference count and are never freed. How are other string fields handled?

    5. pablogsal commented on Feb 1, 2019

      @pablogsal
      Member

      The extra referenced happen here in Python-ast.c :

      value = ast2obj_string(o->type_comment);
          if (!value) goto failed;
          if (_PyObject_SetAttrId(result, &PyId_type_comment, value) == -1)
              goto failed;
          Py_DECREF(value);

      ast2obj_string increments it once and the setattr does it again and then there is only one Py_DECREF.

    6. gvanrossum commented on Feb 1, 2019

      @gvanrossum
      Member

      OK, let me make a PR, I found another leak for type:ignore.

      On Fri, Feb 1, 2019 at 9:22 AM Pablo Galindo Salgado <[email protected]>
      wrote:

      Pablo Galindo Salgado <[email protected]> added the comment:

      The extra referenced happen here in Python-ast.c :

      value = ast2obj_string(o->type_comment);
      if (!value) goto failed;
      if (_PyObject_SetAttrId(result, &PyId_type_comment, value) == -1)
      goto failed;
      Py_DECREF(value);

      ast2obj_string increments it once and the setattr does it again and then
      there is only one Py_DECREF.

      ----------


      Python tracker <[email protected]>
      <https://bugs.python.org/issue35879\>


    7. gvanrossum commented on Feb 1, 2019

      @gvanrossum
      Member

      ast2obj_string increments it once and the setattr does it again and then there is only one Py_DECREF.

      Actually I don't understand how this is the leak. Adding another Py_DECREF(value) causes an immediate crash. I've been trying to follow other paths but haven't found the root yet.

    8. pablogsal commented on Feb 1, 2019

      @pablogsal
      Member

      Hummmm.... I may be missing something but these is what I am seeing:

      BASELINE
      --------

      ❯ ./python.exe -m test test_type_comments -R 10:10
      Run tests sequentially
      0:00:00 load avg: 2.30 [1/1] test_type_comments
      beginning 20 repetitions
      12345678901234567890
      ....................
      test_type_comments leaked [37, 37, 37, 37, 37, 37, 37, 37, 37, 37] references, sum=370
      test_type_comments leaked [11, 12, 11, 11, 11, 11, 11, 11, 11, 11] memory blocks, sum=111
      test_type_comments failed

      == Tests result: FAILURE ==

      1 test failed:
      test_type_comments

      Total duration: 1 sec 28 ms
      Tests result: FAILURE

      EXTRA_DECREF
      ------------

      ❯ git --no-pager diff
      diff --git a/Python/Python-ast.c b/Python/Python-ast.c
      index 1a56e90bca..cb3036dbba 100644
      --- a/Python/Python-ast.c
      +++ b/Python/Python-ast.c
      @@ -2791,6 +2791,7 @@ ast2obj_stmt(void* _o)
               if (_PyObject_SetAttrId(result, &PyId_type_comment, value) == -1)
                   goto failed;
               Py_DECREF(value);
      +        Py_DECREF(value);
               break;
           case AsyncFunctionDef_kind:
               result = PyType_GenericNew(AsyncFunctionDef_type, NULL, NULL);

      ~/github/cpython master ✗
      ❯ ./python.exe -m test test_type_comments -R 10:10
      Run tests sequentially
      0:00:00 load avg: 2.12 [1/1] test_type_comments
      beginning 20 repetitions
      12345678901234567890
      ....................
      test_type_comments leaked [8, 9, 8, 8, 8, 8, 8, 8, 8, 8] memory blocks, sum=81
      test_type_comments failed

      == Tests result: FAILURE ==

      1 test failed:
      test_type_comments

      Total duration: 976 ms
      Tests result: FAILURE

      So my interpretation is that there are still leaks somewhere or, but the extra-decref resolves the reference leaks somehow. Probably the extra DECREF here is not the proper fix, but is as far as I went when I started debugging this morning. Also, if you change one test to do the parsing in a loop:

           def test_funcdef(self):
      -        tree = self.parse(funcdef)
      +        for _ in range(100000):
      +            tree = self.parse(funcdef)
               self.assertEqual(tree.body[0].type_comment, "() -> int")

      And you ran test_type_comments.py with PYTHONDUMPREFS=1:

      WITHOUT EXTRA DECREF
      --------------------

      65 wrapper_descriptor
      75 str
      82 str
      85 str
      86 str
      92 str
      110 str
      416 str
      200015 str

      WITH EXTRA DECREF
      -----------------

      PYTHONDUMPREFS=1 ./python.exe -m test test_type_comments |& cut -d " " -f 3 | uniq -c | sort -n
      ....
        85 str
        86 str
        92 str
       110 str
       416 str
    9. pablogsal commented on Feb 1, 2019

      @pablogsal
      Member

      Note:

      the extra DECREF will fail in other tests because it will deallocate None if ast2obj_object returns Py_None because the comment is NULL.

    10. pablogsal commented on Feb 1, 2019

      @pablogsal
      Member

      I'm almost sure this is not the correct fix, but I think these findings point to that object as the one being leaked. I do not understand how this object should be deallocated and where, I am trying to follow how the other PyObject* (name) is deallocated but I am not finding anything :(

    11. gvanrossum commented on Feb 1, 2019

      @gvanrossum
      Member

      I have been trying to do the same thing and likewise, no luck understanding yet how type comments are different here.

    12. gvanrossum commented on Feb 1, 2019

      @gvanrossum
      Member

      In the meantime here's the fix for the '# type: ignore' leak. gvanrossum@ef3b9e9

    13. gvanrossum commented on Feb 1, 2019

      @gvanrossum
      Member

      Found it! You need to call PyArena_AddPyObject(). I racked my brain and finally found the example code I needed at the end of new_identifier(). Please review quickly so we won't ship 3.8.0a1 with a memory leak.

    14. gvanrossum commented on Feb 1, 2019

      @gvanrossum
      Member

      New changeset d2b4c19 by Guido van Rossum in branch 'master':
      bpo-35879: Fix type comment leaks (GH-11728)
      d2b4c19

    15. self-assigned this
      on Feb 1, 2019
    16. transferred this issue fromon Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    Labels

    3.8 (EOL)end of lifestdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions