Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34k
gh-116946: fully implement GC protocol for _tkinter.Tk{app,tt}Object#138331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-116946: fully implement GC protocol for _tkinter.Tk{app,tt}Object#138331
Uh oh!
There was an error while loading. Please reload this page.
Conversation
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
5f3fc4b to a9c01efCompareUh oh!
There was an error while loading. Please reload this page.
serhiy-storchaka left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to use tp_alloc and tp_free, because these types are not valid base types. Just make them immutable.
picnixz commented Sep 6, 2025 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
Doesn't it mean I would leave the possibility of introducing a cycle in bug fix versions and simply make them all immutable in main? unlike the select.[e]poll and _random.Random, the trace function can have a reference to the current app. Now, you added |
Modules/_tkinter.c Outdated
| type = (PyTypeObject *)Tktt_Type; | ||
| assert(type != NULL); | ||
| assert(type->tp_alloc != NULL); | ||
| v = (TkttObject *)type->tp_alloc(type, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All these types cannot be subclassed. So we can simply replace PyObject_New/PyObject_Free with PyObject_GC_New/PyObject_GC_Del. I think that this would look clearer than with tp_alloc/tp_free.
picnixzSep 6, 2025 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but now the docs explicitly say "don't call those functions directly, use tp_alloc/tp_free". I agree that when possible, we could directly call them, but @ZeroIntensity suggested to follow the docs here.
ZeroIntensitySep 6, 2025 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like using tp_alloc/tp_free more because it's less refactoring if we need to change the type flags. People follow CPython's source code for inspiration in their extensions, so we should follow what's documented. If we want to change the preference, let's update the docs.
Alternatively, we could have a function like this for limited API users:
PyObject*PyType_InvokeAlloc(PyTypeObject*tp){assert(tp!=NULL); assert(type->tp_alloc!=NULL); returntype->tp_alloc(type, 0)}
picnixzSep 6, 2025 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point, I would prefer having a macro to avoid the extra cast everytime... Something that unifies PyObject_[GC_]New(typename, type).
picnixz commented Sep 6, 2025 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
Ok, now I'm utterly confused because I don't understand why I get a segfault. The segfault happens in |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
_tkinter objects_tkinter.Tk{app,tt}Object
vstinner left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Modules/_tkinter.c Outdated
| type = (PyTypeObject *)Tkapp_Type; | ||
| assert(type != NULL); | ||
| assert(type->tp_alloc != NULL); | ||
| v = (TkappObject *)type->tp_alloc(type, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to move the variable definition aside to their first assignment:
| type= (PyTypeObject*)Tkapp_Type; | |
| assert(type!=NULL); | |
| assert(type->tp_alloc!=NULL); | |
| v= (TkappObject*)type->tp_alloc(type, 0); | |
| PyTypeObject*type= (PyTypeObject*)Tkapp_Type; | |
| assert(type!=NULL); | |
| assert(type->tp_alloc!=NULL); | |
| TkappObject*v= (TkappObject*)type->tp_alloc(type, 0); |
Same below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I followed the existing style. Do you want me to move the char *arg as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want me to move the char *arg as well?
As you want. You can leave argv0 as it is to keep the diff small.
picnixzSep 10, 2025 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I would be more comfortable not changing the style of this function. It's a long function, and if we were to use gotos instead for some refactoring, we could benefit for having everything declared at the top. Do you mind I leave it as is?
serhiy-storchaka left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not comfortable with replacing a single line with 5 lines. Why not simply use public API?
picnixz commented Sep 10, 2025
Well.. that's what I wanted to do but now the docs say to do things differently and considering people take inspiration for their extension modules with CPython code it'd be better to match our recommendations. Personally, I would prefer using the exact functions instead of tp_free/tp_alloc. |
ZeroIntensity commented Sep 10, 2025 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
Please create a DPO post to gauge feedback on whether to use |
serhiy-storchaka commented Sep 10, 2025
Well, if you want to use tp_alloc/tp_free, can you just use them, without adding unnecessary lines? |
Modules/_tkinter.c Outdated
| type = (PyTypeObject *)Tktt_Type; | ||
| assert(type != NULL); | ||
| assert(type->tp_alloc != NULL); | ||
| v = (TkttObject *)type->tp_alloc(type, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| type= (PyTypeObject*)Tktt_Type; | |
| assert(type!=NULL); | |
| assert(type->tp_alloc!=NULL); | |
| v= (TkttObject*)type->tp_alloc(type, 0); | |
| v= (TkttObject*)((PyTypeObject*)Tktt_Type)->tp_alloc(type, 0); |
picnixzSep 11, 2025 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, we would need
(TkttObject*)((PyTypeObject*)Tktt_Type)->tp_alloc((PyTypeObject*)Tktt_Type, 0);which is a bit unreadable IMO. So I'll keep the temporary type variable. I would prefer having a macro for calling tp_alloc because it becomes quite annoying if we need to cast to PyTypeObject* everytime twice.
#define_PyObject_New(T, type) \ ((T *)((PyTypeObject *)type)->tp_alloc((PyTypeObject *)type, 0))Uh oh!
There was an error while loading. Please reload this page.
| PyTypeObject *tp = Py_TYPE(op); | ||
| PyObject_GC_UnTrack(op); | ||
| (void)Tktt_Clear(op); | ||
| tp->tp_free(op); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| tp->tp_free(op); | |
| Py_TYPE(op)->tp_free(op); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, this temporary variable is needed as we need Py_DECREF(Py_TYPE(op)) otherwise. The rest of the code base also does this.
Uh oh!
There was an error while loading. Please reload this page.
picnixz commented Sep 10, 2025
Oh you mean, removing the asserts. Well, I can do it though tracking segfaults would be a bit more annoying (at least with asserts, we don't have this issue). I'll just shorten the diff then and let it segfault normally. |
ZeroIntensity commented Sep 10, 2025
Debuggers are typically pretty good at catching null pointer dereferences anyway. |
serhiy-storchaka left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
picnixz commented Sep 11, 2025 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
(I merged main because of conflicts; for the backports, I will need to do them manually as those types are not immutable on 3.13 and 3.14). |
283380a into python:mainUh oh!
There was an error while loading. Please reload this page.
picnixz commented Sep 11, 2025 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
Mmh, something is actually wrong now. Previously, those leaked: importtkinterw=tkinter.Tk() trace=lambda*_, **__: Nonetrace.evil=type(w.tk) w.tk.settrace(trace) w.mainloop()For the timer, it's also possible to make something bad as follows: importtkinterw=tkinter.Tk() func=lambda*_, **__: Nonefunc.evil=type(w.tk.createtimerhandler(1234567, print)) w.tk.createtimerhandler(1234567, func) w.mainloop()But now I have the following crash for the timer reproducer: The first reproducer is fixed however. I think it's because of the extra reference created by the timer handler that is not properly visited or decrefed. I'll first patch this crash and move to backports afterwards. |
…{app,tt}Object` (python#138331)" This reverts commit 283380a.bedevere-bot commented Sep 11, 2025
|
Uh oh!
There was an error while loading. Please reload this page.