Skip to content

nvcc: report subcommand exit codes as exit codes, not signals - #2932

Merged
sylvestre merged 3 commits into
mozilla:mainfrom
bernardladenthin:nvcc-exit-code-status
Oct 24, 2026
Merged

sylvestre merged 3 commits into
mozilla:mainfrom
bernardladenthin:nvcc-exit-code-status

Conversation

@bernardladenthin

@bernardladenthin bernardladenthin commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

When an nvcc subcommand fails inside sccache, the server answers it with exit code -2, and nvcc.rs turns that into an ExitStatus with ExitStatus::from_raw. On Unix from_raw takes a wait status, not an exit code, so -2 reads as "killed by signal 126" and an exit code of 1 as signal 1. Users see sccache: Compiler killed by signal 126 after the real error (e.g. Missing "cubin" file output, #2862), and Compiler killed by signal 1 after a failing fatbinary (#2828), although no process was killed.

compile_result_to_output and error_to_output now encode exit codes as exit codes on Unix (Windows' from_raw already takes the code). aggregate_output keeps the first failing status instead of the maximum of mixed signal numbers and exit codes, so a real signal stays a signal; status_to_code is gone.

Unit tests in nvcc.rs cover the -2 case, exit codes, error_to_output, and that signals are kept; four of them fail without the change. Checked end to end with CUDA 13.4.3 and llama.cpp's ggml-cuda (a list with 90-virtual, which triggers #2862): the build fails with the same exit code 254 and the Missing "cubin" error, without the signal line. test_run_log_no_perm fails locally only because the tests ran as root.

Context: we hit this in java-llama.cpp (Java bindings for llama.cpp), whose CI builds the CUDA (and ROCm) natives through sccache over a shared WebDAV cache. The signal line sent our own diagnosis the wrong way -- we recorded it as nvcc being killed and turned nvcc caching off -- so a correct exit status matters to us. The measurements above were made with that project's toolchain (CUDA 13.4.3, llama.cpp b11815, ggml-cuda).

🤖 Generated with Claude Code

https://claude.ai/code/session_01VszvDmRRPP1UcwozsUMY1L

compile_result_to_output, error_to_output and aggregate_output passed
exit codes to ExitStatus::from_raw, which takes a wait status on Unix.
The server's fatal-error code -2 therefore surfaced as "Compiler killed
by signal 126" (e.g. after `Missing "cubin" file output`), and an exit
code of 1 as signal 1. Exit codes are now encoded as such, and
aggregate_output keeps the first failing status instead of the maximum
of mixed signal numbers and exit codes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VszvDmRRPP1UcwozsUMY1L
@codecov-commenter

codecov-commenter commented Oct 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.29787% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.46%. Comparing base (2eeea91) to head (ff52fda).

Files with missing lines Patch % Lines
src/compiler/compiler.rs 97.18% 2 Missing ⚠️
tests/dist.rs 96.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2932      +/-   ##
==========================================
+ Coverage   78.20%   78.46%   +0.25%     
==========================================
  Files          74       74              
  Lines       43708    43918     +210     
==========================================
+ Hits        34184    34461     +277     
+ Misses       9524     9457      -67     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread src/compiler/nvcc.rs Outdated
// `ExitStatus::from_raw` takes a wait status on Unix, not an exit code: passed directly, the
// server's fatal-error code -2 reads as "killed by signal 126" and an exit code of 1 as signal 1.
#[cfg(unix)]
fn exit_code_status(code: i32) -> process::ExitStatus {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

exit_status in src/dist/mod.rs has the same from_raw(code) problem on unix, no?

bernardladenthin and others added 2 commits October 23, 2026 10:48
`From<ProcessOutput> for process::Output` had the same problem as the
nvcc path: `ProcessOutput::code` is a real exit code (`status.code()` on
the build server), but the client turned it back with
`ExitStatus::from_raw`, which takes a wait status on Unix, so a remote
compile failing with exit code 1 showed up as "killed by signal 1".

The conversion now lives in one place, `util::exit_status_from_code`,
used by both `dist` and `nvcc`, with tests for the helper and for the
`ProcessOutput` round trip.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VszvDmRRPP1UcwozsUMY1L
A unit test with the fake dist client (runs in every CI job) and an end
to end test in tests/dist.rs with a real scheduler and build server: a
compile error found by the compiler on the build server must reach the
client with exit code 1. Without the previous commit the end to end test
fails with "sccache: Compiler killed by signal 1" and exit code 254, and
the unit test sees no exit code at all.

The source uses an undeclared identifier rather than `#error`, because
preprocessing runs locally and an `#error` never reaches the server.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VszvDmRRPP1UcwozsUMY1L
@bernardladenthin

Copy link
Copy Markdown
Contributor Author

@sylvestre yes, same bug in dist: the build server's exit code went through from_raw, so a failed remote compile showed up as "killed by signal 1". Changes since:

  • 323ccf1: shared util::exit_status_from_code, used by nvcc.rs and dist/mod.rs, with tests.
  • ff52fda: a unit test with the fake dist client and test_dist_failed_compile in tests/dist.rs. On main the latter fails with Compiler killed by signal 1 and exit 254.

It also affects every ordinary nvcc compile error, not only Missing "cubin": with CUDA 13.4.3, main reports e.g. an undeclared identifier (nvcc exit 2) or a ptxas error (exit 255) as "killed by signal" with exit 254; with this PR sccache returns nvcc's own exit code.

Tested: the full CI matrix on my fork, where all test jobs pass (Linux, macOS, Windows + CUDA 12.8, dist-tests; only the Codecov upload fails there for lack of a token), plus test_dist_* on Ubuntu 22.04 and CUDA 12.8/13.4 end to end locally.

@sylvestre
sylvestre merged commit 965c019 into mozilla:main Oct 24, 2026
51 checks passed
@sylvestre

Copy link
Copy Markdown
Collaborator

Thanks for your PR

@bernardladenthin
bernardladenthin deleted the nvcc-exit-code-status branch October 24, 2026 10:42
bernardladenthin added a commit to bernardladenthin/java-llama.cpp that referenced this pull request Oct 25, 2026
sccache 0.19.0 (2026-10-24) reports a failing compiler's exit code instead
of "Compiler killed by signal <n>" -- mozilla/sccache#2932, contributed
from this project. SCCACHE_DL_VERSION and the Windows install action move
to it; the SHA-256 pins are the ones mozilla publishes next to the four
assets used here, checked against the downloaded archives.

nvcc goes through sccache by default again (SCCACHE_WRAP_NVCC=false opts
out). It was made opt-in on 2026-10-09 after every CUDA 13.4 job failed
with `Missing "cubin" file output`. That failure needs a PTX-only
compute_90/compute_120 target -- ggml's default list has 90-virtual
(mozilla/sccache#2862) -- and this project builds its own list since
2026-10-11, every arch -real and only 121-virtual PTX-only. Measured in
nvidia/cuda:13.4.3 with llama.cpp b11815: sccache 0.18.0 builds six
ggml-cuda TUs with that list, 108/108 hits warm (19 s vs. 243 s), while
ggml's list fails exactly as CI did.

build.sh's uncached-retry pattern now matches `fatbinary fatal` for the
CUDA 13.3-style failure, which 0.19.0 no longer follows with a signal
line; ordinary CUDA compile errors, which used to print that line too,
now fail fast instead of costing an uncached rebuild.

Verified: check-natives, check-run-scripts, check-shared-files, the
workflow and action YAML, bash -n; the 0.19.0 musl binary runs. The CI
number for the CUDA job is the second run after this (TODO.md).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VszvDmRRPP1UcwozsUMY1L
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants