import collections.abc
import functools
+import gc
import platform
import sys
import textwrap
+import time
import unittest
import weakref
import tkinter
support.gc_collect()
self.assertIsNone(ref())
+ def test_gc_protocol(self):
+ # gh-116946: _tkinter objects implement the GC protocol.
+ self.assertTrue(gc.is_tracked(self.root))
+ tok = self.root.tk.createtimerhandler(10_000_000, lambda: None)
+ try:
+ self.assertTrue(gc.is_tracked(tok))
+ finally:
+ tok.deletetimerhandler()
+
+ def test_timer_fires_after_gc(self):
+ # gh-116946: a pending timer is kept alive by the Tcl event loop, not by
+ # the garbage collector, so collecting it must not cancel it -- it must
+ # still fire even when the Python token has been dropped.
+ fired = []
+ self.root.tk.createtimerhandler(1, lambda: fired.append(1))
+ support.gc_collect()
+ deadline = time.monotonic() + support.SHORT_TIMEOUT
+ while not fired and time.monotonic() < deadline:
+ self.root.update()
+ self.assertEqual(fired, [1])
+
+ def test_pending_timer_at_shutdown(self):
+ # gh-116946: the final garbage collection at interpreter shutdown must
+ # not crash when it visits a timer that is still pending (its type has
+ # already been cleared by the module's tp_clear).
+ assert_python_ok('-c',
+ 'import tkinter\n'
+ 'interp = tkinter.Tcl()\n'
+ 'interp.tk.createtimerhandler(10_000_000, lambda: None)\n')
+
def test_option(self):
self.addCleanup(self.root.option_clear)
self.root.option_add('*Button.background', 'red')
TkappObject *v;
char *argv0;
- v = PyObject_New(TkappObject, (PyTypeObject *) Tkapp_Type);
+ PyTypeObject *tp = (PyTypeObject *)Tkapp_Type;
+ v = (TkappObject *)tp->tp_alloc(tp, 0);
if (v == NULL)
return NULL;
{
TkttObject *v;
- v = PyObject_New(TkttObject, (PyTypeObject *) Tktt_Type);
+ PyTypeObject *tp = (PyTypeObject *)Tktt_Type;
+ v = (TkttObject *)tp->tp_alloc(tp, 0);
if (v == NULL)
return NULL;
return (TkttObject*)Py_NewRef(v);
}
-static void
-Tktt_Dealloc(PyObject *self)
+/* Plain cast, not TkttObject_CAST: the GC can run at shutdown after
+ module_clear() has cleared the global Tktt_Type the macro checks against. */
+
+static int
+Tktt_Clear(PyObject *op)
{
- TkttObject *v = TkttObject_CAST(self);
- PyObject *func = v->func;
- PyObject *tp = (PyObject *) Py_TYPE(self);
+ TkttObject *self = (TkttObject *)op;
+ Py_CLEAR(self->func);
+ return 0;
+}
- Py_XDECREF(func);
+static int
+Tktt_Traverse(PyObject *op, visitproc visit, void *arg)
+{
+ TkttObject *self = (TkttObject *)op;
+ Py_VISIT(Py_TYPE(op));
+ /* Not the extra reference of a pending timer (see Tktt_New): it is owned
+ by the Tcl event loop, so the timer is a GC root, not part of a cycle. */
+ Py_VISIT(self->func);
+ return 0;
+}
- PyObject_Free(self);
+static void
+Tktt_Dealloc(PyObject *op)
+{
+ TkttObject *self = (TkttObject *)op; /* see GC slots above */
+ PyTypeObject *tp = Py_TYPE(op);
+ PyObject_GC_UnTrack(op);
+ /* Cancel any pending timer so its callback cannot fire on freed memory. */
+ if (self->token != NULL) {
+ Tcl_DeleteTimerHandler(self->token);
+ self->token = NULL;
+ }
+ (void)Tktt_Clear(op);
+ tp->tp_free(op);
Py_DECREF(tp);
}
/**** Tkapp Type Methods ****/
+/* Plain casts -- see the Tktt GC slots above. */
+
+static int
+Tkapp_Clear(PyObject *op)
+{
+ TkappObject *self = (TkappObject *)op;
+ Py_CLEAR(self->trace);
+ return 0;
+}
+
+static int
+Tkapp_Traverse(PyObject *op, visitproc visit, void *arg)
+{
+ TkappObject *self = (TkappObject *)op;
+ Py_VISIT(Py_TYPE(op));
+ Py_VISIT(self->trace);
+ return 0;
+}
+
static void
Tkapp_Dealloc(PyObject *op)
{
- TkappObject *self = TkappObject_CAST(op);
- PyTypeObject *tp = Py_TYPE(self);
+ TkappObject *self = (TkappObject *)op;
+ PyTypeObject *tp = Py_TYPE(op);
+ PyObject_GC_UnTrack(op);
if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
/* Deleting the interpreter from another thread aborts the process
("Tcl_AsyncDelete: async handler deleted by the wrong thread").
Tcl_DeleteInterp(Tkapp_Interp(self));
LEAVE_TCL
}
- Py_XDECREF(self->trace);
- PyObject_Free(self);
+ (void)Tkapp_Clear(op);
+ tp->tp_free(op);
Py_DECREF(tp);
DisableEventHook();
}
static PyType_Slot Tktt_Type_slots[] = {
{Py_tp_dealloc, Tktt_Dealloc},
+ {Py_tp_traverse, Tktt_Traverse},
+ {Py_tp_clear, Tktt_Clear},
{Py_tp_repr, Tktt_Repr},
{Py_tp_methods, Tktt_methods},
{0, 0}
Py_TPFLAGS_DEFAULT
| Py_TPFLAGS_DISALLOW_INSTANTIATION
| Py_TPFLAGS_IMMUTABLETYPE
+ | Py_TPFLAGS_HAVE_GC
),
.slots = Tktt_Type_slots,
};
static PyType_Slot Tkapp_Type_slots[] = {
{Py_tp_dealloc, Tkapp_Dealloc},
+ {Py_tp_traverse, Tkapp_Traverse},
+ {Py_tp_clear, Tkapp_Clear},
{Py_tp_methods, Tkapp_methods},
{0, 0}
};
Py_TPFLAGS_DEFAULT
| Py_TPFLAGS_DISALLOW_INSTANTIATION
| Py_TPFLAGS_IMMUTABLETYPE
+ | Py_TPFLAGS_HAVE_GC
),
.slots = Tkapp_Type_slots,
};