python / python/cpython

GC - long_lived_pending is not decremented when a generation-2 object's ref count goes to 0

Open
#122,569 1 comment 0 reactions 1 assignee View on GitHub

@pablogsal is already working on this.

Since Feb 17, 2025.

interpreter-core performance type-bug
Dominant language
Python
Stars
77.2k
Forks
35.9k
PR merge metrics
PR metrics pending

Description

Bug report

Bug description:

Given the idea and documentation around the long_lived_pending / long_lived_total > 0.25 check, I would expect long_lived_pending to be the total number of objects that (1) have survived a generation-1 collection, and (2) are still live (i.e., ref count has not been decremented to 0).

From the behavior I'm seeing, I think only (1) is true. The example code below is contrived to promote many objects into generation-2 and then cause them to be deleted by their ref counts being decremented to 0. Hence there is no reason for Python to perform a generation-2 collection. Nevertheless, it does so repeatedly, collecting nothing each time. This seems sub-optimal, since these collections are expensive and are not doing anything.

import gc
import time
from typing import Any

class Temp:
  pass

accumulating_list = []

def gc_callback(phase: str, info: dict[str, Any]) -> None:
  generation = info['generation']
  if generation == 2:
    print(phase, info, len(gc.get_objects(2)))
  if phase == "stop" and generation == 1:
    # The GC that just ended will have promoted most of the Temps in
    # accumulating_list into generation-2. Drop references to to them here,
    # causing them to be immediately deleted.
    accumulating_list.clear()

gc.callbacks.append(gc_callback)
gc.set_threshold(10, 2, 2)

for x in range(100000):
  accumulating_list.append(Temp())
  time.sleep(0.001)

Is it working as intended that long_lived_pending is not decremented when a corresponding object is deleted by its ref count dropping to 0? This would avoid generation-2 collections except where actually useful. It feels to me like this should happen, although perhaps it's infeasible or too expensive?

Note: Although this is a contrived example, we are seeing this in a real world largeish Python program. It's exacerbated by the default GC thresholds being set really low, which results in even short-lived objects (measured by time) being promoted quickly into generation-2.

CPython versions tested on:

3.11

Operating systems tested on:

macOS

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.