From 8aedb12858a11867cb19d05b2df42bfd0c9f1262 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 1 Jun 2026 13:37:39 +0200 Subject: [PATCH 1/4] gh-150436: Skip subprocess test on STATUS_DLL_INIT_FAILED If a subprocess spawned with CREATE_NEW_CONSOLE creation flag fails with STATUS_DLL_INIT_FAILED return code, skip the test. It's likely a memory allocation failure in the desktop heap memory which caused the DLL init failure. --- Lib/test/support/__init__.py | 15 +++++++++++++++ Lib/test/test_cmd_line.py | 2 ++ Lib/test/test_msvcrt.py | 8 ++++++-- Lib/test/test_subprocess.py | 8 +++++--- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 5b0ae098b636ed6..09b00bfa72a631b 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -3318,3 +3318,18 @@ def control_characters_c0() -> list[str]: _ROOT_IN_POSIX = hasattr(os, 'geteuid') and os.geteuid() == 0 requires_root_user = unittest.skipUnless(_ROOT_IN_POSIX, "test needs root privilege") requires_non_root_user = unittest.skipIf(_ROOT_IN_POSIX, "test needs non-root account") + + +STATUS_DLL_INIT_FAILED = 0xC0000142 +def skip_on_low_heap_memory_subprocess(returncode): + if sys.platform not in ('win32', 'cygwin'): + return + # On Windows, STATUS_DLL_INIT_FAILED is a generic error code that could + # come from any of the DLLs being loaded when a new Python process is + # created. In practice, it's likely a memory allocation failure in the + # desktop heap memory which caused the DLL init failure, especially on + # process created with CREATE_NEW_CONSOLE creation flag. See the article: + # https://learn.microsoft.com/en-us/troubleshoot/windows-server/performance/desktop-heap-limitation-out-of-memory + if returncode == STATUS_DLL_INIT_FAILED: + raise unittest.SkipTest('gh-150436: DLL init failed, likely because ' + 'of low desktop heap memory') diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index 7f9e44d70001b75..ff4c52441140f04 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -1036,6 +1036,7 @@ def test_python_legacy_windows_stdio(self): p = subprocess.run([sys.executable, "-c", code], creationflags=subprocess.CREATE_NEW_CONSOLE, env=env) + support.skip_on_low_heap_memory_subprocess(p.returncode) self.assertEqual(p.returncode, 0) # Then test that FIleIO is used when PYTHONLEGACYWINDOWSSTDIO is set. @@ -1044,6 +1045,7 @@ def test_python_legacy_windows_stdio(self): p = subprocess.run([sys.executable, "-c", code], creationflags=subprocess.CREATE_NEW_CONSOLE, env=env) + support.skip_on_low_heap_memory_subprocess(p.returncode) self.assertEqual(p.returncode, 0) @unittest.skipIf("-fsanitize" in sysconfig.get_config_vars().get('PY_CFLAGS', ()), diff --git a/Lib/test/test_msvcrt.py b/Lib/test/test_msvcrt.py index 1c6905bd1ee5864..03b257bba9760b5 100644 --- a/Lib/test/test_msvcrt.py +++ b/Lib/test/test_msvcrt.py @@ -67,8 +67,12 @@ def run_in_separated_process(self, code): # Run test in a separated process to avoid stdin conflicts. # See: gh-110147 cmd = [sys.executable, '-c', code] - subprocess.run(cmd, check=True, capture_output=True, - creationflags=subprocess.CREATE_NEW_CONSOLE) + try: + subprocess.run(cmd, check=True, capture_output=True, + creationflags=subprocess.CREATE_NEW_CONSOLE) + except subprocess.CalledProcessError as exc: + support.skip_on_low_heap_memory_subprocess(exc.returncode) + raise def test_kbhit(self): code = dedent(''' diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 1a3db527d3d5b83..dbafa16bf936c7f 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -3769,9 +3769,11 @@ def test_creationflags(self): # creationflags argument CREATE_NEW_CONSOLE = 16 sys.stderr.write(" a DOS box should flash briefly ...\n") - subprocess.call(sys.executable + - ' -c "import time; time.sleep(0.25)"', - creationflags=CREATE_NEW_CONSOLE) + rc = subprocess.call(sys.executable + + ' -c "import time; time.sleep(0.25)"', + creationflags=CREATE_NEW_CONSOLE) + support.skip_on_low_heap_memory_subprocess(rc) + self.assertEqual(rc, 0) def test_invalid_args(self): # invalid arguments should raise ValueError From cd3fb79e42e3794397e8911c505002b615598b4b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 1 Jun 2026 13:43:50 +0200 Subject: [PATCH 2/4] Rename to skip_on_low_desktop_heap_memory_subprocess() --- Lib/test/support/__init__.py | 2 +- Lib/test/test_cmd_line.py | 4 ++-- Lib/test/test_msvcrt.py | 2 +- Lib/test/test_subprocess.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 09b00bfa72a631b..cd85ef60a80f4bf 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -3321,7 +3321,7 @@ def control_characters_c0() -> list[str]: STATUS_DLL_INIT_FAILED = 0xC0000142 -def skip_on_low_heap_memory_subprocess(returncode): +def skip_on_low_desktop_heap_memory_subprocess(returncode): if sys.platform not in ('win32', 'cygwin'): return # On Windows, STATUS_DLL_INIT_FAILED is a generic error code that could diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index ff4c52441140f04..3b556ec31445dfb 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -1036,7 +1036,7 @@ def test_python_legacy_windows_stdio(self): p = subprocess.run([sys.executable, "-c", code], creationflags=subprocess.CREATE_NEW_CONSOLE, env=env) - support.skip_on_low_heap_memory_subprocess(p.returncode) + support.skip_on_low_desktop_heap_memory_subprocess(p.returncode) self.assertEqual(p.returncode, 0) # Then test that FIleIO is used when PYTHONLEGACYWINDOWSSTDIO is set. @@ -1045,7 +1045,7 @@ def test_python_legacy_windows_stdio(self): p = subprocess.run([sys.executable, "-c", code], creationflags=subprocess.CREATE_NEW_CONSOLE, env=env) - support.skip_on_low_heap_memory_subprocess(p.returncode) + support.skip_on_low_desktop_heap_memory_subprocess(p.returncode) self.assertEqual(p.returncode, 0) @unittest.skipIf("-fsanitize" in sysconfig.get_config_vars().get('PY_CFLAGS', ()), diff --git a/Lib/test/test_msvcrt.py b/Lib/test/test_msvcrt.py index 03b257bba9760b5..5f67168b0e8e950 100644 --- a/Lib/test/test_msvcrt.py +++ b/Lib/test/test_msvcrt.py @@ -71,7 +71,7 @@ def run_in_separated_process(self, code): subprocess.run(cmd, check=True, capture_output=True, creationflags=subprocess.CREATE_NEW_CONSOLE) except subprocess.CalledProcessError as exc: - support.skip_on_low_heap_memory_subprocess(exc.returncode) + support.skip_on_low_desktop_heap_memory_subprocess(exc.returncode) raise def test_kbhit(self): diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index dbafa16bf936c7f..868b4087b2b0b61 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -3772,7 +3772,7 @@ def test_creationflags(self): rc = subprocess.call(sys.executable + ' -c "import time; time.sleep(0.25)"', creationflags=CREATE_NEW_CONSOLE) - support.skip_on_low_heap_memory_subprocess(rc) + support.skip_on_low_desktop_heap_memory_subprocess(rc) self.assertEqual(rc, 0) def test_invalid_args(self): From 334f693f3a404d8d62b60d8152e511070f036473 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 1 Jun 2026 13:49:24 +0200 Subject: [PATCH 3/4] Add @support.requires_resource('gui') --- Lib/test/test_subprocess.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 868b4087b2b0b61..f944084aaa6d6aa 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -3765,6 +3765,8 @@ def test_startupinfo_copy(self): self.assertEqual(startupinfo.wShowWindow, subprocess.SW_HIDE) self.assertEqual(startupinfo.lpAttributeList, {"handle_list": []}) + # CREATE_NEW_CONSOLE creates a "popup" window. + @support.requires_resource('gui') def test_creationflags(self): # creationflags argument CREATE_NEW_CONSOLE = 16 From f607d4352e208613da0c7dc2dd7e55089a11f4a1 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 1 Jun 2026 16:23:15 +0200 Subject: [PATCH 4/4] Add missing import to test_msvcrt --- Lib/test/test_msvcrt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_msvcrt.py b/Lib/test/test_msvcrt.py index 5f67168b0e8e950..fef86ce323e54d5 100644 --- a/Lib/test/test_msvcrt.py +++ b/Lib/test/test_msvcrt.py @@ -4,6 +4,7 @@ import unittest from textwrap import dedent +from test import support from test.support import os_helper, requires_resource from test.support.os_helper import TESTFN, TESTFN_ASCII