Skip to content

Conversation

picnixz
Copy link
Member

@picnixz picnixz commented Aug 30, 2025

AFAIU, tp_traverse will not be called if Py_TPFLAGS_HAVE_GC is not specified. Quoting the specs:

Instances of heap-allocated types hold a reference to their type. Their traversal function must therefore either visit Py_TYPE(self), or delegate this responsibility by calling tp_traverse of another heap-allocated type (such as a heap-allocated superclass). If they do not, the type object may not be garbage-collected.

As such, I believe that this is the correct fix (that is, we need tp_traverse to be called).

cc @vstinner

}

// explicit fields initialization as PyObject_GC_New() does not change them
self->flushed = 0;
Copy link
Member Author

Choose a reason for hiding this comment

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

We need this to be explicitly initialized otherwise tests fail because flushed would be arbitrary.

if (self->unused_data == NULL)
goto error;

self->bzs = (bz_stream){.opaque = NULL, .bzalloc = NULL, .bzfree = NULL};
Copy link
Member Author

Choose a reason for hiding this comment

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

Without this being set, we have a segmentation fault.

}

// explicit fields initialization as PyObject_GC_New() does not change them
self->eof = 0;
Copy link
Member Author

Choose a reason for hiding this comment

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

Without this being set by default, tests fail as eof is most likely "true" by default (that's because of the call to PyObject_GC_New which initializes the memory but doesn't zero it).

@picnixz picnixz added the 🔨 test-with-refleak-buildbots Test PR w/ refleak buildbots; report in status section label Aug 30, 2025
@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @picnixz for commit a8e8501 🤖

Results will be shown at:

https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F138266%2Fmerge

If you want to schedule another build, you need to add the 🔨 test-with-refleak-buildbots label again.

@bedevere-bot bedevere-bot removed the 🔨 test-with-refleak-buildbots Test PR w/ refleak buildbots; report in status section label Aug 30, 2025

assert(type != NULL && type->tp_alloc != NULL);
self = (BZ2Compressor *)type->tp_alloc(type, 0);
self = PyObject_GC_New(BZ2Compressor, type);
Copy link
Member

Choose a reason for hiding this comment

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

Hm, this seems like introducing additional maintenance for no reason. If a type has Py_TPFLAGS_HAVE_GC set, then tp_alloc will act like PyObject_GC_New anyway, except the data will also be zero-ed (so we wouldn't need the additional change below).

Copy link
Member Author

@picnixz picnixz Aug 31, 2025

Choose a reason for hiding this comment

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

I don't like those kind of "will act like" [...]. I prefer using the public API correctly rather than relying on implementation details. I know it's an additional maintenance burden but I think it's worth it because we would follow our own rules.

Also, I think we should perhaps add a function for zero data with PyObject_GC_New to prevent these additional changes while using public functions (and not just accessing tp_alloc directly).

Copy link
Member

Choose a reason for hiding this comment

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

I get what you're saying, but this really isn't an implementation detail. The docs explicitly tell users to prefer tp_alloc instead of manually allocating objects. The only place where I'd say it's ok to use PyObject_GC_New is when using the limited API.

Copy link
Member Author

@picnixz picnixz Aug 31, 2025

Choose a reason for hiding this comment

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

Then we're not reading the same docs: https://docs.python.org/3/c-api/gcsupport.html#supporting-cycle-detection

Constructors for container types must conform to two rules:
The memory for the object must be allocated using PyObject_GC_New or PyObject_GC_NewVar.

And https://docs.python.org/3/c-api/typeobj.html#c.Py_TPFLAGS_HEAPTYPE:

Heap types should also support garbage collection as they can form a reference cycle with their own module object

and in https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_traverse:

More information about Python’s garbage collection scheme can be found in section Supporting Cyclic Garbage Collection.

All those documented area point to the page where we explicitly ask users to use PyObject_GC_New[Var]. I haven't seen anywhere something recommending tp_alloc. And https://docs.python.org/3/c-api/type.html#c.PyType_GenericAlloc doesn't document that this can be used instead of GC_New + GC_Track.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this was part of the big object lifecycle documentation update in 3.14: #125962

Copy link
Member

@ZeroIntensity ZeroIntensity left a comment

Choose a reason for hiding this comment

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

Sorry to block like this, but I feel strongly -1 on the switch from tp_alloc to PyObject_GC_New. In 3.14 and 3.15, all of the C API documentation asks that you choose tp_alloc over manual object allocation functions.


assert(type != NULL && type->tp_alloc != NULL);
self = (BZ2Compressor *)type->tp_alloc(type, 0);
self = PyObject_GC_New(BZ2Compressor, type);
Copy link
Member

Choose a reason for hiding this comment

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

I get what you're saying, but this really isn't an implementation detail. The docs explicitly tell users to prefer tp_alloc instead of manually allocating objects. The only place where I'd say it's ok to use PyObject_GC_New is when using the limited API.

@bedevere-app
Copy link

bedevere-app bot commented Aug 31, 2025

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

@picnixz
Copy link
Member Author

picnixz commented Aug 31, 2025

In 3.14 and 3.15, all of the C API documentation asks that you choose tp_alloc over manual object allocation functions.

Ah that's why. The stable docs being 3.13, this wasn't indicated! I can switch to do this then, though I really hate invoking directly the slot function.

@picnixz
Copy link
Member Author

picnixz commented Aug 31, 2025

I'll update the other PRs for 3.14+, that I can do. However, does the behavior also hold in 3.13? or is a 3.14+ feature?

@ZeroIntensity
Copy link
Member

The behavior holds for 3.13, we just changed the preference in the docs in 3.14.

though I really hate invoking directly the slot function.

How come? I think it's just nicer to use. If you change the type from GC to non-GC, for example, you don't have to update all your tp_alloc and tp_free calls.


PyTypeObject *tp = Py_TYPE(self);
tp->tp_free((PyObject *)self);
PyObject_GC_Del(self);
Copy link
Member

Choose a reason for hiding this comment

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

Please also prefer tp_free over PyObject_GC_Del. The new docs say that too:

Do not call this directly to free an object’s memory; call the type’s tp_free slot instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I just swa that as well, so I'm going to add another commit (I'm modifying the other 3 PRs at the same time)

@picnixz
Copy link
Member Author

picnixz commented Aug 31, 2025

How come? I think it's just nicer to use. If you change the type from GC to non-GC, for example, you don't have to update all your tp_alloc and tp_free calls.

I would have preferred invoking a consumer function that is, PyObject_GenericFree(op) so that I don't need to use tp->tp_free(op). It looks nicer to me, but those are really style preferences.

@ZeroIntensity
Copy link
Member

I'd be okay with a PyType_InvokeAlloc or PyType_InvokeFree on the basis that it would make things easier for limited API users, but that's out of the scope of these PRs.

@picnixz
Copy link
Member Author

picnixz commented Aug 31, 2025

Hum, now it feels wrong but is it ok that the GC is tracking the object when I'm not done yet with setting its fields? because it appears that now alloc functions automatically track the object after the call and this violates the recommendations for https://docs.python.org/3.14/c-api/gcsupport.html#c.PyObject_GC_Track.

@ZeroIntensity
Copy link
Member

Yeah, that's fine. In practice, most object won't call into Python code before they're fully initialized, and even if one does, that's why there's a big red note at gc.get_referrers.

@picnixz
Copy link
Member Author

picnixz commented Aug 31, 2025

Yeah, but this is still wrong. What if someone is calling some method on the argument? that would call into Python code. I think we should also have a function for doing the alloc but not tracking the object yet. For now, I'll live with this but I really think this should be documented at the level of tp_alloc as well (it doesn't say that it tracks the object...)

@ZeroIntensity
Copy link
Member

Yeah, but this is still wrong. What if someone is calling some method on the argument?

Unless they're playing with gc.get_referrers, nothing should happen, right? The GC won't crash because the fields are correctly zeroed out by tp_alloc.

documented at the level of tp_alloc as well (it doesn't say that it tracks the object...)

Sure, do you want to make the PR?

@picnixz
Copy link
Member Author

picnixz commented Aug 31, 2025

I have made the requested changes; please review again.

Unless they're playing with gc.get_referrers, nothing should happen, right

I hope you're right. OTOH, users could first construct the fields separately and set them afterwards (though they won't be able to rely on Py_DECREF(self) to release fields that were successfully created).

Sure, do you want to make the PR?

I don't mind but I think it's also an easy contribution for newcomers.

@picnixz
Copy link
Member Author

picnixz commented Aug 31, 2025

Thanks for humoring me with tp_alloc, this looks good.

I think we should still backport the recommendations to 3.13 if nothing changed on our side (if this was always the behavior of tp_alloc & co, then it's probably worth doing this). However, we should still update https://docs.python.org/3.14/c-api/gcsupport.html#supporting-cycle-detection because it says "Use GC_New" and under "GC_New" it says "use tp_alloc" and this would be confusing.

@picnixz
Copy link
Member Author

picnixz commented Aug 31, 2025

I'll create a doc issue for that because there are two tasks:

@ZeroIntensity
Copy link
Member

I think we should still backport the recommendations to 3.13 if nothing changed on our side (if this was always the behavior of tp_alloc & co, then it's probably worth doing this)

Sounds good, but up to you on whether to put in the effort, because 3.14's documentation will be the default in a month anyway.

@picnixz
Copy link
Member Author

picnixz commented Aug 31, 2025

Mmmh. Right. No need for the docs backports.

@AA-Turner
Copy link
Member

Off-topic, but I generally default to reading https://docs.python.org/dev/ -- this is the version that reflects HEAD, whereas /3/ reflects what downstream users should be using.

A

@picnixz picnixz merged commit 9be91f6 into python:main Sep 1, 2025
48 of 51 checks passed
@picnixz picnixz deleted the fix/gc/bz2-heap-types-116946 branch September 1, 2025 08:21
@picnixz picnixz added needs backport to 3.13 bugs and security fixes needs backport to 3.14 bugs and security fixes labels Sep 1, 2025
@miss-islington-app
Copy link

Thanks @picnixz for the PR 🌮🎉.. I'm working now to backport this PR to: 3.13.
🐍🍒⛏🤖

@miss-islington-app
Copy link

Thanks @picnixz for the PR 🌮🎉.. I'm working now to backport this PR to: 3.14.
🐍🍒⛏🤖

@miss-islington-app
Copy link

Sorry, @picnixz, I could not cleanly backport this to 3.13 due to a conflict.
Please backport using cherry_picker on command line.

cherry_picker 9be91f6a20ed2fd9b491c3e779dc45c7392f60ca 3.13

miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Sep 1, 2025
@bedevere-app
Copy link

bedevere-app bot commented Sep 1, 2025

GH-138320 is a backport of this pull request to the 3.14 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.14 bugs and security fixes label Sep 1, 2025
picnixz added a commit to picnixz/cpython that referenced this pull request Sep 1, 2025
@bedevere-app
Copy link

bedevere-app bot commented Sep 1, 2025

GH-138322 is a backport of this pull request to the 3.13 branch.

@bedevere-app bedevere-app bot removed the needs backport to 3.13 bugs and security fixes label Sep 1, 2025
picnixz added a commit that referenced this pull request Sep 1, 2025
picnixz added a commit to picnixz/cpython that referenced this pull request Sep 1, 2025
picnixz added a commit to picnixz/cpython that referenced this pull request Sep 1, 2025
kumaraditya303 pushed a commit that referenced this pull request Sep 1, 2025
…H-138322, GH-138323, GH-138326) (#138337)

* Revert "[3.13] gh-116946: fully implement GC protocol for `bz2` objects (GH-138266) (#138322)"

This reverts commit 90036f5.

* Revert "[3.13] gh-116946: fully implement GC protocol for `lzma` objects (GH-138288) (#138323)"

This reverts commit 828682d.

* Revert "[3.13] gh-116946: fully implement GC protocol for `_hashlib` objects (GH-138289) (#138326)"

This reverts commit 21b5932.
kumaraditya303 pushed a commit that referenced this pull request Sep 1, 2025
…, GH-138288, GH-138289) (#138338)

* Revert "gh-116946: fully implement GC protocol for `bz2` objects (#138266)"

This reverts commit 9be91f6.

* Revert "gh-116946: fully implement GC protocol for `lzma` objects (#138288)"

This reverts commit 3ea16f9.

* Revert "gh-116946: fully implement GC protocol for `_hashlib` objects (#138289)"

This reverts commit 6f1dd95.
lkollar pushed a commit to lkollar/cpython that referenced this pull request Sep 9, 2025
lkollar pushed a commit to lkollar/cpython that referenced this pull request Sep 9, 2025
…ythonGH-138266, pythonGH-138288, pythonGH-138289) (python#138338)

* Revert "pythongh-116946: fully implement GC protocol for `bz2` objects (python#138266)"

This reverts commit 9be91f6.

* Revert "pythongh-116946: fully implement GC protocol for `lzma` objects (python#138288)"

This reverts commit 3ea16f9.

* Revert "pythongh-116946: fully implement GC protocol for `_hashlib` objects (python#138289)"

This reverts commit 6f1dd95.
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