Skip to content

gh-116946: Implement the GC protocol for _tkinter tkapp and tktimertoken#152310

Open
serhiy-storchaka wants to merge 1 commit into
python:mainfrom
serhiy-storchaka:tkinter-gc-protocol
Open

gh-116946: Implement the GC protocol for _tkinter tkapp and tktimertoken#152310
serhiy-storchaka wants to merge 1 commit into
python:mainfrom
serhiy-storchaka:tkinter-gc-protocol

Conversation

@serhiy-storchaka

Copy link
Copy Markdown
Member

The internal _tkinter.tkapp and _tkinter.tktimertoken types are heap types implemented in C but never implemented the garbage collector protocol, so reference cycles through a Tcl interpreter's trace function or a timer handler's callback could not be collected.

They now implement tp_traverse and tp_clear, set Py_TPFLAGS_HAVE_GC, allocate through the type's tp_alloc, and untrack on deallocation.

Two design points differ from the earlier, reverted attempt (gh-138331, reverted by gh-138807 over the gh-138789/gh-138791 regression):

  • A pending timer is kept alive by the Tcl event loop, which fires it even after the Python token is dropped. It is therefore treated as a GC root -- only its callback is traversed, not the extra reference owned by the event loop -- so collecting it never cancels a live timer. test_timer_fires_after_gc guards this.
  • The GC slots use a plain cast rather than the type-checking macro, because the collector may visit a surviving object during interpreter shutdown, after module clearing has reset the global type pointers (the source of the gh-138791: fix crash when visiting a non-consumed timer handler #138789 shutdown crash). test_pending_timer_at_shutdown guards this.

This is independent of and complementary to gh-80937 / #152294: it does not traverse the command table, so it does not collect the createcommand cycle, and that fix does not collect interpreter/timer cycles.

…imertoken

The _tkinter.tkapp and _tkinter.tktimertoken types never implemented the
garbage collector protocol, so reference cycles through an interpreter's
trace function or a timer handler's callback could not be collected.

A pending timer is kept alive by the Tcl event loop, which fires it even
after the Python token is dropped, so it is treated as a GC root (only its
callback is traversed) and collecting it never cancels a live timer.  The
GC slots use a plain cast rather than the type-checking macro, since the
collector may visit a surviving object at shutdown after module clearing
has reset the global type pointers.  Deallocation cancels any pending timer
so its callback cannot run on freed memory.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant