Skip to content

Re-entering pairwise.__next__() leaks references #109786

Description

@serhiy-storchaka

Re-entering the __next__() method of itertools.pairwise leaks references, because old hold a borrowed reference when the __next__() method of the underlying iterator is called. It may potentially lead to the use of a freed memory and a crash, but I only have reproducer for leaks.

Even if the re-entrant call is not very meaningful, it should correctly count references. There are several ways to fix this issue. I choose the simplest one which matches the current results (only without leaks) in most of simple tests, even if there is no other reasons to prefer these results.

cc @rhettinger

Linked PRs

Activity

  1. added a commit that references this issue on Sep 23, 2023
  2. added
    type-bugAn unexpected behavior, bug, or error
    3.11only security fixes
    3.12only security fixes
    3.13only security fixes
    on Sep 23, 2023
  3. rhettinger commented on Sep 23, 2023

    @rhettinger
    Contributor

    Let's get the other issue fixed first. This one is somewhat exotic and the PR makes a mess of the code.

  4. pochmann commented on Sep 23, 2023

    @pochmann
    Contributor

    I don't see your leak reproducer, wrote one based on your test:

    Leak reproducer

    Whole code at Attempt This Online!

    incref(x) artificially increases the refcount of x:

    def incref(x):
        class I:
            count = 0
            def __iter__(self):
                return self
            def __next__(self):
                self.count += 1
                if self.count == 1:
                    return next(pairs)
                if self.count == 3:
                    return x
                return None
        pairs = pairwise(I())
        next(pairs)

    Demo increasing an object's refcount by 10000:

    x = object()
    print(f'refcount before:', sys.getrefcount(x))
    for _ in range(10000):
        incref(x)
    gc.collect()
    print('refcount after:', sys.getrefcount(x))

    Output:

    refcount before: 2
    refcount after: 10002
    

    Demo leaking 10000 objects of 10 kB each:

    tracemalloc.start()
    for _ in range(10000):
        incref(bytes(10000))
    gc.collect()
    print('leaked memory:', tracemalloc.get_traced_memory()[0] // 10**6, 'MB')

    Output:

    leaked memory: 100 MB
    
  5. serhiy-storchaka commented on Sep 24, 2023

    @serhiy-storchaka
    MemberAuthor

    I added them as tests. You can run tests with the -R option to see leaks.

  6. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Nov 25, 2023
  7. added a commit that references this issue on Dec 4, 2023
  8. added 2 commits that reference this issue on Dec 4, 2023
  9. added 2 commits that reference this issue on Dec 4, 2023
  10. added a commit that references this issue on Feb 11, 2024
  11. added a commit that references this issue on Sep 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.11only security fixes3.12only security fixes3.13only security fixesstdlibStandard 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