Fix Tk 9 crash on startup and generation-thread callbacks - #28
Open
ddisisto wants to merge 3 commits into
Open
Conversation
Tcl/Tk 9 dropped the deprecated `trace variable` command, so every
`var.trace('w', ...)` raises TclError: bad option "variable" and the app
cannot open a pane. Use the trace_add('write', ...) form, which has been
available since Python 3.6 and passes callbacks the same arguments.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Generation runs in a worker thread, and two of its callbacks reach Tk:
delete_failed_nodes called tree_updated() directly, exactly what the
comment two functions above it warns against, so any failed generation
ran the view refresh on the worker thread. Under Tk 9 that aborts the
process with "Unknown display passed to Tk_CreateErrorHandler".
post_generation avoided that with event_generate("<<NewNodes>>"), but
virtual events are queued per-thread, so an event generated off the main
thread is never delivered and edit_new_nodes never ran.
Replace both with a queue the main thread drains on a timer, which is the
usual way to get work out of a worker thread and into tkinter.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The retry decorator called on_failure(*args, **kwargs) without returning
its value, so an exhausted retry chain returned None rather than the
handler's intended ("", None). Callers that unpack the result then failed
with "cannot unpack non-iterable NoneType object", which hides whatever
error actually exhausted the retries.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three provider-agnostic fixes found while getting loom running on a current Linux distro. No new dependencies, no behaviour changes beyond the bugs described.
1.
Variable.tracewas removed in Tk 9Tcl/Tk 9 dropped the deprecated
trace variablesubcommand, which is what Python'sVariable.trace(mode, callback)shells out to. On any Tk 9 build, everyvar.trace('w', ...)call raises:There are 10 of these across
components/dialogs.py,components/modules.py,components/templates.pyandview/panes.py, and they fire while building panes, so the app can't open a pane at all on a distro that has moved to Tk 9.Replaced with the
trace_add('write', ...)form. That has been available since Python 3.6 and passes the callback the same(name, index, mode)arguments, so no call sites needed changing.2. Generation-thread callbacks never reached the main thread
Generation runs in a worker thread, and two of its callbacks end up touching Tk:
delete_failed_nodescalledself.tree_updated(...)directly — exactly what the# DO NOT CALL FROM THREAD: self.tree_updated()comment two functions above it warns against. It runs the whole view refresh on the worker thread. Under Tk 9 that aborts the process outright:So any failed generation killed the app rather than just dropping the failed nodes.
post_generationavoided that by going throughself.app.event_generate("<<NewNodes>>", when="tail")instead. But Tk queues virtual events per-thread, so an event generated from a worker thread is never delivered to the main thread's event loop, andedit_new_nodessimply never ran. I checked this empirically rather than assuming: firing the event from a worker thread produces no dispatch, while the identical call on the main thread dispatches normally. It's a silent no-op rather than a crash, which is presumably why it went unnoticed.Both now go through a small
queue.Queuethat the main thread drains from anafter()timer (call_on_main_thread/drain_main_thread_queue), which is the conventional way to hand work from a worker thread into tkinter. Callbacks that raise are caught and traced so one bad callback doesn't stop the drain loop.Verified by driving
default_generatefrom worker threads against a real API and asserting both callbacks execute onMainThread.3.
retry'son_failureresult was discardedIn
util/util.py, the retry decorator's failure branch calledon_failure(*args, **kwargs)without returning it. Every exhausted retry chain therefore returnedNoneinstead of the handler's intended("", None), and the caller's unpack blew up with:which buries whatever error actually exhausted the retries. One-line fix:
return on_failure(...).Tested on Python 3.11 with Tcl/Tk 9.0 on Arch/Manjaro.
I have some further work sitting in a branch that I've deliberately kept out of this PR, since it's more opinionated than a bug fix:
pandas==1.3.3pulls a numpy that won't compile against modern toolchains, andtk==0.1.0inrequirements.txtis an unrelated PyPI package rather than the tkinter bindings (those ship with CPython).uv/pyproject.tomlsetup, as an alternative to the requirements.txt flow.Would you welcome any of those as follow-up PRs, or would you rather keep the project as it stands? Completely fine either way — happy to just leave these three fixes here if the project is in maintenance mode.
🤖 Generated with Claude Code