Skip to content

Commit db3d2f0

Browse files
committed
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.
1 parent 1cda28d commit db3d2f0

2 files changed

Lines changed: 74 additions & 1 deletion

File tree

‎cuda_core/build_hooks.py‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
import re
1515
import sys
1616
import tempfile
17+
import threading
1718
import zipfile
19+
from concurrent.futures import ThreadPoolExecutor
1820
from pathlib import Path
1921

2022
import Cython as _Cython
@@ -698,7 +700,17 @@ def build_editable(wheel_directory, config_settings=None, metadata_directory=Non
698700

699701
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
700702
debug = config_settings.get("debug", False) if config_settings else False
701-
config_key = _build_cuda_core(debug=debug)
703+
if sys.platform == "win32" and COMPILE_FOR_COVERAGE:
704+
# Importing the coverage-built bindings can exhaust the interpreter's
705+
# main-thread stack. Set the worker stack inside pip's build subprocess.
706+
previous_stack_size = threading.stack_size(8 * 1024 * 1024)
707+
try:
708+
with ThreadPoolExecutor(max_workers=1) as pool:
709+
config_key = pool.submit(_build_cuda_core, debug=debug).result()
710+
finally:
711+
threading.stack_size(previous_stack_size)
712+
else:
713+
config_key = _build_cuda_core(debug=debug)
702714
wheel_name = _build_meta.build_wheel(wheel_directory, config_settings, metadata_directory)
703715
record_build_config(config_key)
704716
return wheel_name

‎cuda_core/tests/test_build_hooks.py‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,67 @@ def test_record_writes_stamp(self, stamp):
277277
assert stamp.read_text().strip() == "cu13-gnu-debug"
278278

279279

280+
class TestCoverageBuildStack:
281+
@pytest.mark.parametrize("platform", ["win32", "linux"])
282+
@pytest.mark.parametrize("coverage", [False, True])
283+
@pytest.mark.agent_authored(model="gpt-6")
284+
def test_wheel_preparation_thread_and_stack_restoration(self, monkeypatch, platform, coverage):
285+
caller = threading.current_thread()
286+
previous_stack_size = threading.stack_size()
287+
prepared = []
288+
recorded = []
289+
290+
def prepare(debug):
291+
prepared.append((threading.current_thread(), debug))
292+
return "prepared-config"
293+
294+
def build(wheel_directory, config_settings, metadata_directory):
295+
assert threading.current_thread() is caller
296+
assert (wheel_directory, config_settings, metadata_directory) == ("dist", {"debug": True}, "metadata")
297+
return "cuda_core.whl"
298+
299+
monkeypatch.setattr(build_hooks, "sys", types.SimpleNamespace(platform=platform))
300+
monkeypatch.setattr(build_hooks, "COMPILE_FOR_COVERAGE", coverage)
301+
monkeypatch.setattr(build_hooks, "_build_cuda_core", prepare)
302+
monkeypatch.setattr(build_hooks._build_meta, "build_wheel", build)
303+
monkeypatch.setattr(build_hooks, "record_build_config", recorded.append)
304+
305+
assert build_hooks.build_wheel("dist", {"debug": True}, "metadata") == "cuda_core.whl"
306+
worker, debug = prepared[0]
307+
assert (worker is not caller) == (platform == "win32" and coverage)
308+
assert debug is True
309+
assert threading.stack_size() == previous_stack_size
310+
assert recorded == ["prepared-config"]
311+
312+
@pytest.mark.agent_authored(model="gpt-6")
313+
def test_preparation_failure_propagates_and_restores_stack(self, monkeypatch):
314+
failure = RuntimeError("preparation failed")
315+
previous_stack_size = threading.stack_size()
316+
317+
def fail(debug):
318+
raise failure
319+
320+
monkeypatch.setattr(build_hooks, "sys", types.SimpleNamespace(platform="win32"))
321+
monkeypatch.setattr(build_hooks, "COMPILE_FOR_COVERAGE", True)
322+
monkeypatch.setattr(build_hooks, "_build_cuda_core", fail)
323+
monkeypatch.setattr(
324+
build_hooks._build_meta,
325+
"build_wheel",
326+
lambda *_args: pytest.fail("failed preparation must not build a wheel"),
327+
)
328+
monkeypatch.setattr(
329+
build_hooks,
330+
"record_build_config",
331+
lambda _key: pytest.fail("failed preparation must not be stamped"),
332+
)
333+
334+
with pytest.raises(RuntimeError) as exc_info:
335+
build_hooks.build_wheel("dist")
336+
337+
assert exc_info.value is failure
338+
assert threading.stack_size() == previous_stack_size
339+
340+
280341
class TestBuildHookStamping:
281342
@pytest.mark.agent_authored(model="grok-4.6")
282343
def test_wheel_records_exact_prepared_config_after_success(self, monkeypatch):

0 commit comments

Comments
 (0)