GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||
GeezerVison is showing github.com — tkinter: os.dup2() on fds 1/2 breaks every subsequent Tcl interpreter on Windows · Issue #161620 · python/cpython Open Open Copy link Labels OS-windowsextension-modulesC modules in the Modules dirC modules in the Modules dirtopic-tkintertype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error DescriptionBug reportBug description:On Windows, once a Tcl interpreter has built its standard channels, a later The file is present and readable the whole time. At the instant of the failure Python can This is not GUI-specific — ReproducerFails 10/10 runs here, with stdout going to a console, a pipe, a file or import os, sys, tempfile, tkinter saved = {fd: os.dup(fd) for fd in (1, 2)} tmp = {fd: tempfile.TemporaryFile() for fd in (1, 2)} use_tcl = "--tcl" in sys.argv def new_interpreter(): if use_tcl: ip = tkinter.Tcl() # no GUI at all ip.eval("fconfigure stdout") # make Tcl build its std channels del ip else: tkinter.Tk().destroy() for i in range(30): for fd in (1, 2): # redirect fds 1 and 2 ... os.dup2(tmp[fd].fileno(), fd) try: new_interpreter() except Exception as exc: for fd in (1, 2): os.dup2(saved[fd], fd) print(f"interpreter {i} failed:\n{exc}") sys.exit(1) for fd in (1, 2): # ... and put them back os.dup2(saved[fd], fd) print("30 interpreters, no failure") It usually fails at interpreter 1 or 2. Two interpreters and two What is happening
Handle
Step 1 is the necessary condition, and it is testable on its own. A bare Tcl interpreter that never touches its standard channels never fails; add one
Tk matters only because The converse holds too. If the handles Tcl caches are private duplicates that nothing else can close, the failure disappears. Done from Python with
Why this looks like a
|
pytest configuration, 30 tests creating one Tk() each |
runs failed |
|---|---|
--capture=fd (the default) |
16/20 |
-s / --capture=sys / --capture=tee-sys |
0/20 |
one test creating 30 interpreters in a loop, --capture=fd |
0/20 |
That is pytest-dev/pytest#10364, open since 2022. Applying the pin described above from a conftest.py takes that suite from 10/20 runs failing to 0/20.
I have not traced the step from "Tcl is holding a stale standard handle" to the specific couldn't read file ...: no such file or directory. The reported errno is clearly not the real one, and the upstream ticket describes a different surface symptom from the same cause (failing writes, EBADF) — so I assume it is stale rather than meaningful, but I have not proved it.
Two things that did not pan out, in case they save someone time: Tcl does not close standard handles it does not own (a planted sentinel handle survives interpreter create + destroy), and creating a Tk root before the redirection does not help, because the first dup2 closes the original stdout handle too.
3.14 (3.14.4, Windows installer build, Tcl/Tk 8.6.15). I do not have other versions on this machine, so I have not checked whether main behaves differently — nothing in the mechanism looks version-specific.
Windows (Windows 11 Pro, build 26200).
It isn't common to build the std-channels. What you normally do in python is to use pythons stdout etc. and redirect any tcl proc, e.g puts & gets. As in the linked ticket stated, how should the TCL be aware of any changes, if you build them then you are responsible for them, no?
Further reading:
https://wiki.tcl-lang.org/page/redefining+stdout
see discussion in this wiki: https://wiki.tcl-lang.org/page/stdout
redirecting could be done like this https://stackoverflow.com/a/74578994/13629335
Cannot reproduce on Win11 with python 3.15.2 and tkinter 9.0; ran the script 10 times.
Just wanted to check if you call exit on the TCL interpreter if that solves your issue.