Skip to content

gh-157176: Fix GC tracking in PyStructSequence_New - #157179

Open
ashm-dev wants to merge 3 commits into
python:mainfrom
ashm-dev:gh-157176
Open

ashm-dev wants to merge 3 commits into
python:mainfrom
ashm-dev:gh-157176

Conversation

@ashm-dev

@ashm-dev ashm-dev commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

@ashm-dev

ashm-dev commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

@sergey-miryanov, could you please review this PR?

Comment thread Objects/structseq.c Outdated
@vstinner

Copy link
Copy Markdown
Member

The reported issue is a leak when modifying structseq types. But this PR changes how structseq instances are tracked by the GC. Can you explain me the relationship between the two?

@ashm-dev

Copy link
Copy Markdown
Contributor Author

The reported issue is a leak when modifying structseq types. But this PR changes how structseq instances are tracked by the GC. Can you explain me the relationship between the two?

They are the same bug. Modifying the type creates the cycle type -> tp_dict -> t -> type. It leaks because t is untracked, so the GC cannot see the t -> type edge. Tracking the instance makes that edge visible and lets the GC collect the cycle.

@vstinner

Copy link
Copy Markdown
Member

See also #157447 change which is limited to sys.unraisablehook.

@serhiy-storchaka

Copy link
Copy Markdown
Member

Claude initially wrote exactly this change. But it is potentially breaking, the following PyObject_GC_Track() in the user code will crash.

@ashm-dev

Copy link
Copy Markdown
Contributor Author

Claude initially wrote exactly this change. But it is potentially breaking, the following PyObject_GC_Track() in the user code will crash.

But the fix in your PR doesn't resolve the leak from my issue.

@sergey-miryanov

Copy link
Copy Markdown
Contributor

But the fix in your PR doesn't resolve the leak from my issue.

Your both issues have a common root but fix from #157447 shouldn't fix your issue.

@ashm-dev

ashm-dev commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

Friendly ping @serhiy-storchaka @vstinner — what's the plan here?

@vstinner

vstinner commented Oct 5, 2026

Copy link
Copy Markdown
Member

Sadly, changing the public PyStructSequence_New() C API sounds dangerous. As @serhiy-storchaka wrote, it can break existing C extensions which call PyObject_GC_Track().

You can change internal C APIs or modify stdlib extensions to call PyObject_GC_Track() after PyStructSequence_New(). I'm not sure of what's the best approach.

Also, if it's recommended to track objects created by PyStructSequence_New() in the GC, you should mention it in PyStructSequence_New() documentation.

@ashm-dev

ashm-dev commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Should I revert the PyStructSequence_New() change, add PyObject_GC_Track() to stdlib callers (time, os, etc.) similar to #157447, and document the tracking requirement in PyStructSequence_New() docs?

@vstinner

vstinner commented Oct 5, 2026

Copy link
Copy Markdown
Member

Should I revert the PyStructSequence_New() change, add PyObject_GC_Track() to stdlib callers (time, os, etc.) similar to #157447, and document the tracking requirement in PyStructSequence_New() docs?

Yes, but only add PyObject_GC_Track() if an object can contains other objects which can be involved in a reference cycle.

@ashm-dev

ashm-dev commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

@vstinner Done. Reverted changes to PyStructSequence_New(), added PyObject_GC_Track() to time.struct_time and sys.get_asyncgen_hooks(), documented the requirement in PyStructSequence_New() docs, and updated tests.

@vstinner vstinner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

        # Instances created via Python or stdlib callers that may participate
        # in cycles are GC-tracked; simple instances like os.stat_result are not.
        self.assertTrue(gc.is_tracked(time.gmtime()))

time.gmtime() members are all integers. How can it be involved in a reference cycle?

Code from the issue:

./python -c "import time; t = time.gmtime(); type(t).refcyle = t;"

In your example, you modify the type, not a structseq instance.

I'm not sure that this change is correct.

cc @serhiy-storchaka

@vstinner

vstinner commented Oct 5, 2026

Copy link
Copy Markdown
Member

Instead, the type should be made immutable: #157176 (comment).

@serhiy-storchaka

Copy link
Copy Markdown
Member

AFAIK only the argument of sys.unraisablehook contains other objects which can be involved in a reference cycle (this is #157447).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants