From cdec34792e1ab95dd840d0ddd82da9878cfa3cdc Mon Sep 17 00:00:00 2001 From: Michiel de Hoon Date: Tue, 21 Apr 2026 15:16:17 +0900 Subject: [PATCH 1/8] fix for issue 140146 --- Lib/test/test_tkinter/test_tkinter_pipe.py | 39 ++++++++++++++++++++++ Modules/_tkinter.c | 31 ++++++++++++----- 2 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 Lib/test/test_tkinter/test_tkinter_pipe.py diff --git a/Lib/test/test_tkinter/test_tkinter_pipe.py b/Lib/test/test_tkinter/test_tkinter_pipe.py new file mode 100644 index 00000000000000..67f27d64e8bbbb --- /dev/null +++ b/Lib/test/test_tkinter/test_tkinter_pipe.py @@ -0,0 +1,39 @@ +# test_tkinter_pipe.py +import unittest +import subprocess +import sys +from test import support + + +@unittest.skipUnless(support.has_subprocess_support, "test requires subprocess") +class TkinterPipeTest(unittest.TestCase): + + def test_tkinter_pipe(self): + proc = subprocess.Popen([sys.executable, "-i", "-u"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc.stdin.write(b"import tkinter\n") + proc.stdin.write(b"interpreter = tkinter.Tcl()\n") + + proc.stdin.write(b"print('hello')\n") + proc.stdin.flush() + stdout = proc.stdout.readline() + stdout = stdout.decode() + self.assertEqual(stdout.strip(), 'hello') + + proc.stdin.write(b"print('hello again')\n") + proc.stdin.flush() + stdout = proc.stdout.readline() + stdout = stdout.decode() + self.assertEqual(stdout.strip(), 'hello again') + + proc.stdin.write(b"print('goodbye')\n") + proc.stdin.write(b"quit()\n") + stdout, stderr = proc.communicate() + stdout = stdout.decode() + self.assertEqual(stdout.strip(), 'goodbye') + + +if __name__ == "__main__": + unittest.main() diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index bbe2a428454e0c..2f5446459c387e 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -3352,10 +3352,10 @@ static PyMethodDef moduleMethods[] = }; #ifdef WAIT_FOR_STDIN +#ifndef MS_WINDOWS static int stdin_ready = 0; -#ifndef MS_WINDOWS static void MyFileProc(void *clientData, int mask) { @@ -3368,22 +3368,37 @@ static PyThreadState *event_tstate = NULL; static int EventHook(void) { -#ifndef MS_WINDOWS +#ifdef MS_WINDOWS + HANDLE hStdin; + DWORD type; +#else int tfile; + stdin_ready = 0; #endif PyEval_RestoreThread(event_tstate); - stdin_ready = 0; errorInCmd = 0; -#ifndef MS_WINDOWS +#ifdef MS_WINDOWS + hStdin = GetStdHandle(STD_INPUT_HANDLE); + type = GetFileType(hStdin); + while (!errorInCmd) { +#else tfile = fileno(stdin); Tcl_CreateFileHandler(tfile, TCL_READABLE, MyFileProc, NULL); -#endif while (!errorInCmd && !stdin_ready) { +#endif int result; #ifdef MS_WINDOWS - if (_kbhit()) { - stdin_ready = 1; - break; + if (type == FILE_TYPE_CHAR) { + if (_kbhit()) break; + } + else if (type == FILE_TYPE_PIPE) { + DWORD available; + if (PeekNamedPipe(hStdin, NULL, 0, NULL, &available, NULL)) { + if (available > 0) break; + } + else { + if (GetLastError() == ERROR_BROKEN_PIPE) break; + } } #endif Py_BEGIN_ALLOW_THREADS From 00962caff36e7a0ba18c1e4ce191b6143b2ca2d8 Mon Sep 17 00:00:00 2001 From: Michiel de Hoon Date: Tue, 21 Apr 2026 16:08:57 +0900 Subject: [PATCH 2/8] add blurb --- .../Windows/2026-04-21-16-07-11.gh-issue-140146.TAcUHA.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Windows/2026-04-21-16-07-11.gh-issue-140146.TAcUHA.rst diff --git a/Misc/NEWS.d/next/Windows/2026-04-21-16-07-11.gh-issue-140146.TAcUHA.rst b/Misc/NEWS.d/next/Windows/2026-04-21-16-07-11.gh-issue-140146.TAcUHA.rst new file mode 100644 index 00000000000000..312a9965e224ff --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2026-04-21-16-07-11.gh-issue-140146.TAcUHA.rst @@ -0,0 +1,3 @@ +Avoid tkinter to hang on Windows if stdin is redirected to a pipe in an +interactive session. This is helpful for testing interactive usage of +tkinter from a script, for example as part of the cpython test suite. From ea84b7b3e99bbaf515b0cb2531d1c6394649b796 Mon Sep 17 00:00:00 2001 From: Michiel de Hoon Date: Tue, 21 Apr 2026 16:39:15 +0900 Subject: [PATCH 3/8] add one test --- Lib/test/test_tkinter/test_tkinter_pipe.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_tkinter/test_tkinter_pipe.py b/Lib/test/test_tkinter/test_tkinter_pipe.py index 67f27d64e8bbbb..8054b33bf05f66 100644 --- a/Lib/test/test_tkinter/test_tkinter_pipe.py +++ b/Lib/test/test_tkinter/test_tkinter_pipe.py @@ -8,8 +8,24 @@ @unittest.skipUnless(support.has_subprocess_support, "test requires subprocess") class TkinterPipeTest(unittest.TestCase): - def test_tkinter_pipe(self): - proc = subprocess.Popen([sys.executable, "-i", "-u"], + def test_tkinter_pipe_buffered(self): + args = [sys.executable, "-i"] + proc = subprocess.Popen(args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + proc.stdin.write(b"import tkinter\n") + proc.stdin.write(b"interpreter = tkinter.Tcl()\n") + proc.stdin.write(b"print('hello')\n") + proc.stdin.write(b"print('goodbye')\n") + proc.stdin.write(b"quit()\n") + stdout, stderr = proc.communicate() + stdout = stdout.decode() + self.assertEqual(stdout.split(), ['hello', 'goodbye']) + + def test_tkinter_pipe_unbuffered(self): + args = [sys.executable, "-i", "-u"] + proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) From 5bc255b86d89ac7d8425203a8bb3c984bf783027 Mon Sep 17 00:00:00 2001 From: mdehoon Date: Mon, 29 Jun 2026 09:20:25 +0900 Subject: [PATCH 4/8] Update _tkinter.c to remove errorInCmd Remove errorInCmd as a condition for the while-loop, as #gh-139145 has now been merged. --- Modules/_tkinter.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 5ae1ba59e47e1f..1673fb68f1e21e 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -3477,13 +3477,13 @@ EventHook(void) #ifdef MS_WINDOWS hStdin = GetStdHandle(STD_INPUT_HANDLE); type = GetFileType(hStdin); - while (!errorInCmd) { + while (1) { #else tfile = fileno(stdin); Tcl_CreateFileHandler(tfile, TCL_READABLE, MyFileProc, (void *)(Py_intptr_t)tfile); -#endif while (!stdin_ready) { +#endif int result; #ifdef MS_WINDOWS if (type == FILE_TYPE_CHAR) { From b524821703a70380c6414bbe155ba4214f26e082 Mon Sep 17 00:00:00 2001 From: mdehoon Date: Mon, 29 Jun 2026 17:13:19 +0900 Subject: [PATCH 5/8] Add an arm for FILE_TYPE_DISK Adding a breaking arm for FILE_TYPE_DISK (python -i < file), as a disk file is always readable. --- Modules/_tkinter.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 1673fb68f1e21e..62a79128d9726c 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -3498,6 +3498,9 @@ EventHook(void) if (GetLastError() == ERROR_BROKEN_PIPE) break; } } + else if (type == FILE_TYPE_DISK) { + break; + } #endif Py_BEGIN_ALLOW_THREADS if(tcl_lock)PyThread_acquire_lock(tcl_lock, 1); From e71f6bad9e67481d1cdd42598f765e053ced7bf4 Mon Sep 17 00:00:00 2001 From: mdehoon Date: Mon, 29 Jun 2026 17:13:50 +0900 Subject: [PATCH 6/8] Update Misc/NEWS.d/next/Windows/2026-04-21-16-07-11.gh-issue-140146.TAcUHA.rst Co-authored-by: Serhiy Storchaka --- .../next/Windows/2026-04-21-16-07-11.gh-issue-140146.TAcUHA.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Windows/2026-04-21-16-07-11.gh-issue-140146.TAcUHA.rst b/Misc/NEWS.d/next/Windows/2026-04-21-16-07-11.gh-issue-140146.TAcUHA.rst index 312a9965e224ff..2d3e42c86fd521 100644 --- a/Misc/NEWS.d/next/Windows/2026-04-21-16-07-11.gh-issue-140146.TAcUHA.rst +++ b/Misc/NEWS.d/next/Windows/2026-04-21-16-07-11.gh-issue-140146.TAcUHA.rst @@ -1,3 +1,3 @@ -Avoid tkinter to hang on Windows if stdin is redirected to a pipe in an +Prevent :mod:`tkinter` from hanging on Windows if stdin is redirected to a pipe in an interactive session. This is helpful for testing interactive usage of tkinter from a script, for example as part of the cpython test suite. From 475b7375b178d259d877ff3f9fc2c1385c83ec85 Mon Sep 17 00:00:00 2001 From: mdehoon Date: Mon, 29 Jun 2026 17:14:50 +0900 Subject: [PATCH 7/8] Update Lib/test/test_tkinter/test_tkinter_pipe.py If the test fails, let it fail quickly. Co-authored-by: Serhiy Storchaka --- Lib/test/test_tkinter/test_tkinter_pipe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_tkinter/test_tkinter_pipe.py b/Lib/test/test_tkinter/test_tkinter_pipe.py index 8054b33bf05f66..dbbfde172619dd 100644 --- a/Lib/test/test_tkinter/test_tkinter_pipe.py +++ b/Lib/test/test_tkinter/test_tkinter_pipe.py @@ -19,7 +19,7 @@ def test_tkinter_pipe_buffered(self): proc.stdin.write(b"print('hello')\n") proc.stdin.write(b"print('goodbye')\n") proc.stdin.write(b"quit()\n") - stdout, stderr = proc.communicate() + stdout, stderr = proc.communicate(timeout=support.SHORT_TIMEOUT) stdout = stdout.decode() self.assertEqual(stdout.split(), ['hello', 'goodbye']) From ab8a136e275393b5de13ec8b79157ebae0ae48fe Mon Sep 17 00:00:00 2001 From: mdehoon Date: Mon, 29 Jun 2026 17:15:03 +0900 Subject: [PATCH 8/8] Update Lib/test/test_tkinter/test_tkinter_pipe.py If the test fails, let it fail quickly. Co-authored-by: Serhiy Storchaka --- Lib/test/test_tkinter/test_tkinter_pipe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_tkinter/test_tkinter_pipe.py b/Lib/test/test_tkinter/test_tkinter_pipe.py index dbbfde172619dd..775d0ff5e19b0c 100644 --- a/Lib/test/test_tkinter/test_tkinter_pipe.py +++ b/Lib/test/test_tkinter/test_tkinter_pipe.py @@ -46,7 +46,7 @@ def test_tkinter_pipe_unbuffered(self): proc.stdin.write(b"print('goodbye')\n") proc.stdin.write(b"quit()\n") - stdout, stderr = proc.communicate() + stdout, stderr = proc.communicate(timeout=support.SHORT_TIMEOUT) stdout = stdout.decode() self.assertEqual(stdout.strip(), 'goodbye')