From db3d2f016e35d47bf0a8c2c07a893c074a98b7e7 Mon Sep 17 00:00:00 2001 From: Jason Pascucci Date: Mon, 2 Nov 2026 11:05:50 -0800 Subject: [PATCH 1/2] Fix Windows coverage wheel-build stack overflow closes #3330 Run cuda-core wheel preparation on an 8 MiB worker-thread stack for Windows coverage builds. This addresses the stack overflow observed after switching to uv-managed Python, whose executable reserves less stack than the previous official Python build. Restore the previous stack setting after preparation and propagate failures. Other build configurations retain their existing behavior. Validation: 136 build-hook tests passed, 3 skipped; Ruff and formatting checks passed. Windows coverage CI confirmation remains pending. --- cuda_core/build_hooks.py | 14 ++++++- cuda_core/tests/test_build_hooks.py | 61 +++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/cuda_core/build_hooks.py b/cuda_core/build_hooks.py index 46a5f70602c..390d1137059 100644 --- a/cuda_core/build_hooks.py +++ b/cuda_core/build_hooks.py @@ -14,7 +14,9 @@ import re import sys import tempfile +import threading import zipfile +from concurrent.futures import ThreadPoolExecutor from pathlib import Path import Cython as _Cython @@ -698,7 +700,17 @@ def build_editable(wheel_directory, config_settings=None, metadata_directory=Non def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): debug = config_settings.get("debug", False) if config_settings else False - config_key = _build_cuda_core(debug=debug) + if sys.platform == "win32" and COMPILE_FOR_COVERAGE: + # Importing the coverage-built bindings can exhaust the interpreter's + # main-thread stack. Set the worker stack inside pip's build subprocess. + previous_stack_size = threading.stack_size(8 * 1024 * 1024) + try: + with ThreadPoolExecutor(max_workers=1) as pool: + config_key = pool.submit(_build_cuda_core, debug=debug).result() + finally: + threading.stack_size(previous_stack_size) + else: + config_key = _build_cuda_core(debug=debug) wheel_name = _build_meta.build_wheel(wheel_directory, config_settings, metadata_directory) record_build_config(config_key) return wheel_name diff --git a/cuda_core/tests/test_build_hooks.py b/cuda_core/tests/test_build_hooks.py index b4e3361db5e..23e76cf5eec 100644 --- a/cuda_core/tests/test_build_hooks.py +++ b/cuda_core/tests/test_build_hooks.py @@ -277,6 +277,67 @@ def test_record_writes_stamp(self, stamp): assert stamp.read_text().strip() == "cu13-gnu-debug" +class TestCoverageBuildStack: + @pytest.mark.parametrize("platform", ["win32", "linux"]) + @pytest.mark.parametrize("coverage", [False, True]) + @pytest.mark.agent_authored(model="gpt-6") + def test_wheel_preparation_thread_and_stack_restoration(self, monkeypatch, platform, coverage): + caller = threading.current_thread() + previous_stack_size = threading.stack_size() + prepared = [] + recorded = [] + + def prepare(debug): + prepared.append((threading.current_thread(), debug)) + return "prepared-config" + + def build(wheel_directory, config_settings, metadata_directory): + assert threading.current_thread() is caller + assert (wheel_directory, config_settings, metadata_directory) == ("dist", {"debug": True}, "metadata") + return "cuda_core.whl" + + monkeypatch.setattr(build_hooks, "sys", types.SimpleNamespace(platform=platform)) + monkeypatch.setattr(build_hooks, "COMPILE_FOR_COVERAGE", coverage) + monkeypatch.setattr(build_hooks, "_build_cuda_core", prepare) + monkeypatch.setattr(build_hooks._build_meta, "build_wheel", build) + monkeypatch.setattr(build_hooks, "record_build_config", recorded.append) + + assert build_hooks.build_wheel("dist", {"debug": True}, "metadata") == "cuda_core.whl" + worker, debug = prepared[0] + assert (worker is not caller) == (platform == "win32" and coverage) + assert debug is True + assert threading.stack_size() == previous_stack_size + assert recorded == ["prepared-config"] + + @pytest.mark.agent_authored(model="gpt-6") + def test_preparation_failure_propagates_and_restores_stack(self, monkeypatch): + failure = RuntimeError("preparation failed") + previous_stack_size = threading.stack_size() + + def fail(debug): + raise failure + + monkeypatch.setattr(build_hooks, "sys", types.SimpleNamespace(platform="win32")) + monkeypatch.setattr(build_hooks, "COMPILE_FOR_COVERAGE", True) + monkeypatch.setattr(build_hooks, "_build_cuda_core", fail) + monkeypatch.setattr( + build_hooks._build_meta, + "build_wheel", + lambda *_args: pytest.fail("failed preparation must not build a wheel"), + ) + monkeypatch.setattr( + build_hooks, + "record_build_config", + lambda _key: pytest.fail("failed preparation must not be stamped"), + ) + + with pytest.raises(RuntimeError) as exc_info: + build_hooks.build_wheel("dist") + + assert exc_info.value is failure + assert threading.stack_size() == previous_stack_size + + class TestBuildHookStamping: @pytest.mark.agent_authored(model="grok-4.6") def test_wheel_records_exact_prepared_config_after_success(self, monkeypatch): From ae4e287c5d8cc703ee14808436976a7efe8f97d5 Mon Sep 17 00:00:00 2001 From: Jason Pascucci Date: Mon, 2 Nov 2026 12:05:01 -0800 Subject: [PATCH 2/2] Ralf J's suggestion --- .github/workflows/coverage.yml | 8 ++++ cuda_core/build_hooks.py | 14 +------ cuda_core/tests/test_build_hooks.py | 61 ----------------------------- 3 files changed, 9 insertions(+), 74 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 88d1f11d2da..2e998453e5c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -250,6 +250,14 @@ jobs: - name: Set up MSVC uses: step-security/msvc-dev-cmd@22c98154b708dbd743e6f27a933cf6ceba3305c4 # v1.13.1 + - name: Raise the interpreter's stack reserve to 8 MiB + # Importing the coverage-built cuda.bindings.driver overflows the + # 2,000,000-byte main-thread stack of python-build-standalone (#3330). + run: | + base_py="$(.venv/Scripts/python -c 'import sys; print(sys._base_executable)')" + editbin -nologo -stack:8388608 "$base_py" + dumpbin -nologo -headers "$base_py" | grep -i "size of stack reserve" + - name: Set up mini CTK uses: ./.github/actions/fetch_ctk continue-on-error: false diff --git a/cuda_core/build_hooks.py b/cuda_core/build_hooks.py index 390d1137059..46a5f70602c 100644 --- a/cuda_core/build_hooks.py +++ b/cuda_core/build_hooks.py @@ -14,9 +14,7 @@ import re import sys import tempfile -import threading import zipfile -from concurrent.futures import ThreadPoolExecutor from pathlib import Path import Cython as _Cython @@ -700,17 +698,7 @@ def build_editable(wheel_directory, config_settings=None, metadata_directory=Non def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): debug = config_settings.get("debug", False) if config_settings else False - if sys.platform == "win32" and COMPILE_FOR_COVERAGE: - # Importing the coverage-built bindings can exhaust the interpreter's - # main-thread stack. Set the worker stack inside pip's build subprocess. - previous_stack_size = threading.stack_size(8 * 1024 * 1024) - try: - with ThreadPoolExecutor(max_workers=1) as pool: - config_key = pool.submit(_build_cuda_core, debug=debug).result() - finally: - threading.stack_size(previous_stack_size) - else: - config_key = _build_cuda_core(debug=debug) + config_key = _build_cuda_core(debug=debug) wheel_name = _build_meta.build_wheel(wheel_directory, config_settings, metadata_directory) record_build_config(config_key) return wheel_name diff --git a/cuda_core/tests/test_build_hooks.py b/cuda_core/tests/test_build_hooks.py index 23e76cf5eec..b4e3361db5e 100644 --- a/cuda_core/tests/test_build_hooks.py +++ b/cuda_core/tests/test_build_hooks.py @@ -277,67 +277,6 @@ def test_record_writes_stamp(self, stamp): assert stamp.read_text().strip() == "cu13-gnu-debug" -class TestCoverageBuildStack: - @pytest.mark.parametrize("platform", ["win32", "linux"]) - @pytest.mark.parametrize("coverage", [False, True]) - @pytest.mark.agent_authored(model="gpt-6") - def test_wheel_preparation_thread_and_stack_restoration(self, monkeypatch, platform, coverage): - caller = threading.current_thread() - previous_stack_size = threading.stack_size() - prepared = [] - recorded = [] - - def prepare(debug): - prepared.append((threading.current_thread(), debug)) - return "prepared-config" - - def build(wheel_directory, config_settings, metadata_directory): - assert threading.current_thread() is caller - assert (wheel_directory, config_settings, metadata_directory) == ("dist", {"debug": True}, "metadata") - return "cuda_core.whl" - - monkeypatch.setattr(build_hooks, "sys", types.SimpleNamespace(platform=platform)) - monkeypatch.setattr(build_hooks, "COMPILE_FOR_COVERAGE", coverage) - monkeypatch.setattr(build_hooks, "_build_cuda_core", prepare) - monkeypatch.setattr(build_hooks._build_meta, "build_wheel", build) - monkeypatch.setattr(build_hooks, "record_build_config", recorded.append) - - assert build_hooks.build_wheel("dist", {"debug": True}, "metadata") == "cuda_core.whl" - worker, debug = prepared[0] - assert (worker is not caller) == (platform == "win32" and coverage) - assert debug is True - assert threading.stack_size() == previous_stack_size - assert recorded == ["prepared-config"] - - @pytest.mark.agent_authored(model="gpt-6") - def test_preparation_failure_propagates_and_restores_stack(self, monkeypatch): - failure = RuntimeError("preparation failed") - previous_stack_size = threading.stack_size() - - def fail(debug): - raise failure - - monkeypatch.setattr(build_hooks, "sys", types.SimpleNamespace(platform="win32")) - monkeypatch.setattr(build_hooks, "COMPILE_FOR_COVERAGE", True) - monkeypatch.setattr(build_hooks, "_build_cuda_core", fail) - monkeypatch.setattr( - build_hooks._build_meta, - "build_wheel", - lambda *_args: pytest.fail("failed preparation must not build a wheel"), - ) - monkeypatch.setattr( - build_hooks, - "record_build_config", - lambda _key: pytest.fail("failed preparation must not be stamped"), - ) - - with pytest.raises(RuntimeError) as exc_info: - build_hooks.build_wheel("dist") - - assert exc_info.value is failure - assert threading.stack_size() == previous_stack_size - - class TestBuildHookStamping: @pytest.mark.agent_authored(model="grok-4.6") def test_wheel_records_exact_prepared_config_after_success(self, monkeypatch):