-
-
Notifications
You must be signed in to change notification settings - Fork 32.9k
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
Conversation
5f3fc4b
to
a9c01ef
Compare
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.
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
.
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.
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);
return 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.
At this point, I would prefer having a macro to avoid the extra cast everytime... Something that unifies PyObject_[GC_]New(typename, type)
.
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
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.
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?
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?
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. |
Please create a DPO post to gauge feedback on whether to use |
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); |
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))
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.
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. |
Debuggers are typically pretty good at catching null pointer dereferences anyway. |
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.
(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). |
Mmh, something is actually wrong now. Previously, those leaked: import tkinter
w = tkinter.Tk()
trace = lambda *_, **__: None
trace.evil = type(w.tk)
w.tk.settrace(trace)
w.mainloop() For the timer, it's also possible to make something bad as follows: import tkinter
w = tkinter.Tk()
func = lambda *_, **__: None
func.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.
|
Uh oh!
There was an error while loading. Please reload this page.