Skip to content

[no-ci] Fix Windows coverage test stack overflows - #3342

Merged
jpascucci-nv merged 3 commits into
NVIDIA:mainfrom
jpascucci-nv:stack_overflows
Nov 3, 2026
Merged

jpascucci-nv merged 3 commits into
NVIDIA:mainfrom
jpascucci-nv:stack_overflows

Conversation

@jpascucci-nv

Copy link
Copy Markdown
Contributor

Description

closes #3341

Raise the GPU runner's base Python executable stack reserve to at least 8 MiB using a standard-library PE-header helper. Write a patched copy and replace the executable after Python exits, avoiding the need for editbin on the GPU runner. Preserve the existing editbin build-job step. (note, we don't have editbin on gpu runners, so this is the workaround)

run_pytest_with_stack.py enlarges only the worker thread running pytest. Samples, subprocess tests, and multiprocessing workers start fresh Python interpreters with the executable's original stack reserve, so they still overflow. The wrapper also leaves Python's main thread waiting for pytest, which can delay deferred cleanup scheduled through Py_AddPendingCall.

Run pytest directly on the main thread with the patched interpreter and remove the worker-thread wrapper. Keep continue-on-error enabled.

Validation: 22 tests passed; Ruff, formatting, and workflow syntax checks passed. Verified patched PE32 and PE32+ executables with objdump. Windows GPU CI confirmation remains pending.

Checklist

  • I have read the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

Raise the GPU runner's base Python executable stack reserve to at least
8 MiB using a standard-library PE-header helper. Write a patched copy
and replace the executable after Python exits, avoiding the need for
editbin on the GPU runner. Preserve the existing editbin build-job step.

run_pytest_with_stack.py enlarges only the worker thread running pytest.
Samples, subprocess tests, and multiprocessing workers start fresh Python
interpreters with the executable's original stack reserve, so they still
overflow. The wrapper also leaves Python's main thread waiting for pytest,
which can delay deferred cleanup scheduled through Py_AddPendingCall.

Run pytest directly on the main thread with the patched interpreter and
remove the worker-thread wrapper. Keep continue-on-error enabled.

Validation: 22 tests passed; Ruff, formatting, and workflow syntax checks
passed. Verified patched PE32 and PE32+ executables with objdump.
Windows GPU CI confirmation remains pending.
@jpascucci-nv jpascucci-nv added this to the cuda.core 1.3.0 milestone Nov 3, 2026
@jpascucci-nv
jpascucci-nv requested review from juenglin and rwgk November 3, 2026 17:45
@jpascucci-nv jpascucci-nv self-assigned this Nov 3, 2026
@jpascucci-nv jpascucci-nv added the CI/CD CI/CD infrastructure label Nov 3, 2026
@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Summary

Summary by CodeRabbit

  • Chores
    • Updated Windows build and coverage workflows to configure a larger interpreter stack reserve.
    • The coverage workflow now runs the cuda.bindings and cuda.core test suites directly from the installation directory. The cuda.pathfinder test command remains unchanged.
    • Removed the custom test-running utility; these changes affect CI workflows rather than the end-user experience.

The hardened Python action can set and verify a Windows interpreter stack reserve. The Windows wheel-build and coverage jobs request 8 MiB. Coverage runs the cuda.bindings and cuda.core tests directly with python -m pytest and removes the custom thread-based helper.

Changes: Windows coverage stack reserve

Layer / File(s) Summary
Configure and verify interpreter stack reserve
.github/actions/setup-hardened-python/action.yml
The action adds an optional Windows-only stack-reserve input. When set, it raises the base executable’s PE stack reserve to the requested positive byte count and verifies the running interpreter’s main-thread reserve.
Use the configured interpreter in Windows CI
.github/workflows/coverage.yml, ci/tools/run_pytest_with_stack.py
The Windows wheel-build and coverage jobs request an 8 MiB stack reserve. Coverage runs cuda.bindings and cuda.core tests with python -m pytest from the install root. The custom thread-based pytest helper is deleted.

Assessment against linked issues

Objective Addressed Explanation
[#3341] Prevent Windows Coverage tests from failing due to stack overflows. ✅

Out-of-scope changes

Code Change Explanation
Set an 8 MiB stack reserve in the Windows wheel-build job (.github/workflows/coverage.yml:245-249). Issue #3341 identifies stack-overflow failures in the Coverage test suites; it does not state a wheel-build requirement.

Possibly related issues

Suggested labels: bug, cuda.bindings, cuda.core, github_actions

Suggested reviewers: andy-jost

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: Minimal · up to 7af7f

The change is mergeable after normal checks; Windows GPU CI confirmation remains pending.

- [ ] Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.
Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 4f28c66 and 7af7f9b.

Files selected for processing: 2
  • .github/actions/setup-hardened-python/action.yml
  • .github/workflows/coverage.yml

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 9 remain after this review.

Comment thread .github/workflows/coverage.yml Resolved

@rwgk rwgk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ralf: whoa ... that's quite an involved workaround. Did you already ask the runner team about pre-installing MSVC tools?

But LGTM, except the CR finding.

GPT-6.1-Sol ultra:

  • P1 — Strip CR before converting the interpreter path (coverage.yml:393). Windows Python emits CRLF; Bash command substitution removes only LF, leaving python.exe\r. The patcher receives an invalid source path, stopping Windows coverage before tests run. Replace print(sys._base_executable) with sys.stdout.write(sys._base_executable). This confirms CodeRabbit’s existing finding. (CPython source)

No additional findings. All 22 helper tests pass on Linux; native Windows GPU validation remains pending.

@jpascucci-nv

Copy link
Copy Markdown
Contributor Author

@rwgk it doesn't make sense to have msvc on gpu runners, that would be a lot of work.

@juenglin

juenglin commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

@rwgk @jpascucci-nv Following up on Ralf's editbin question and Jason's answer that installing MSVC on the GPU runners isn't practical.

editbin only ships with the MSVC C++ toolset (Visual Studio or Build Tools for Visual Studio). Neither Windows nor the Windows SDK includes it, so Jason is right that the GPU runners would need a multi-GB toolset install. The PyPI package pefile can make the same header edit, and it needs only Python. That keeps Ralf's one-liner spirit without a repo-owned PE parser or its tests.

I'd also move this into setup-hardened-python, so the build job and the test job use one mechanism (the build job's editbin step goes away too), behind an opt-in input:

# .github/actions/setup-hardened-python/action.yml
inputs:
  stack-reserve:
    description: >-
      Windows only. Raise the base interpreter's main-thread stack reserve to at
      least this many bytes. Empty (default) leaves the interpreter unchanged.
    default: ""

New last step of the action, after the ssl check and pip update, so pefile is only downloaded through a verified interpreter:

    - name: Raise the interpreter's stack reserve
      if: runner.os == 'Windows' && inputs.stack-reserve != ''
      shell: bash --noprofile --norc -xeuo pipefail {0}
      env:
        PYTHON: ${{ steps.venv.outputs.python-path }}
        STACK_RESERVE: ${{ inputs.stack-reserve }}
      run: |
        # sys.stdout.write, not print: Windows Python ends lines with CRLF.
        base="$("${PYTHON}" -c 'import sys; sys.stdout.write(sys._base_executable)')"
        # Windows can't overwrite a running executable: write a patched copy,
        # then replace the original after the patching interpreter has exited.
        uv run --no-project --isolated --with pefile==2024.8.26 --python "${base}" python - "${base}" "${STACK_RESERVE}" <<'EOF'
        import sys, pefile
        path, reserve = sys.argv[1], int(sys.argv[2])
        pe = pefile.PE(path)
        pe.OPTIONAL_HEADER.SizeOfStackReserve = max(pe.OPTIONAL_HEADER.SizeOfStackReserve, reserve)
        pe.write(path + ".stack-reserve")
        EOF
        mv -f -- "${base}.stack-reserve" "${base}"
        # Check the effect on the real binary, not just the header bytes.
        "${base}" - "${STACK_RESERVE}" <<'EOF'
        import ctypes, sys
        lo, hi = ctypes.c_size_t(), ctypes.c_size_t()
        ctypes.windll.kernel32.GetCurrentThreadStackLimits(ctypes.byref(lo), ctypes.byref(hi))
        print("main-thread stack reserve:", hi.value - lo.value)
        sys.exit(hi.value - lo.value < int(sys.argv[1]))
        EOF

In coverage.yml, both Windows jobs would then call uses: $/.github/actions/setup-hardened-python with stack-reserve: 8388608. ci/tools/prepare_windows_python_stack.py, its tests, and the build job's editbin step can all be removed. The switch to pytest on the main thread and the removal of run_pytest_with_stack.py stay as they are in this PR.

Why this shape:

  • Opt-in on purpose. Users run on 2 MB (uv, python-build-standalone) or 3 MB (python.org) interpreters. If every CI job had 8 MB, a change that pushed normal stack use past 2 MB would pass CI and crash for users. Only the Cython line-tracing coverage builds are known to need more.
  • Per job. The action installs interpreters under runner.temp, so the patch can't leak into other jobs, even on persistent self-hosted runners.
  • Covers everything. The base interpreter, the venv launcher's child process, subprocesses, multiprocessing workers, and threads created without an explicit stack size (Windows gives them the reserve from the executable header) all get the larger stack.

On tests: with pefile there's no parser of ours left to unit-test. Failures are loud: a broken executable means Python won't start, and a patch that doesn't take effect means the coverage build overflows the stack again. The GetCurrentThreadStackLimits check covers the one silent failure, the patch not taking effect while the code doesn't yet need the extra stack, and it runs against the real binary. A green Windows coverage run is enough validation.

@juenglin juenglin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to simplify this.

@jpascucci-nv

Copy link
Copy Markdown
Contributor Author

@juenglin Implemented your suggestions.

@juenglin

juenglin commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Dispatched a coverage run on ci/coverage-stack-fix (the PR head pushed to upstream for workflow_dispatch): https://github.com/NVIDIA/cuda-python/actions/runs/41696905251

@jpascucci-nv

Copy link
Copy Markdown
Contributor Author

I had started a CI coverage run against this PR a few minutes back: https://github.com/NVIDIA/cuda-python/actions/runs/41694943698

@rwgk rwgk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm: I used my existing codex session to re-review after the substantial changes: no findings.

@jpascucci-nv
jpascucci-nv enabled auto-merge (squash) November 3, 2026 18:52
@jpascucci-nv
jpascucci-nv merged commit 4fb742c into NVIDIA:main Nov 3, 2026
34 checks passed
@juenglin

juenglin commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Coverage CI passed — all 37 checks green on run 41696905251 (temp branch ci/coverage-stack-fix has been cleaned up).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI/CD CI/CD infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: Windows CI Coverage was failing with stack overflows.

3 participants