Skip to content

fix(cargo-oxide): install the cached backend by rename, not in place - #1329

Merged
nihalpasham merged 6 commits into
NVIDIA:mainfrom
midagedev:fix/atomic-backend-install
Oct 19, 2026
Merged

nihalpasham merged 6 commits into
NVIDIA:mainfrom
midagedev:fix/atomic-backend-install

Conversation

@midagedev

@midagedev midagedev commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

What this adds

cargo oxide keeps one shared copy of the codegen backend at ~/.cargo/cuda-oxide/librustc_codegen_cuda.so, and every rustc that compiles a kernel maps that file for the whole compile. The installer used to refresh it with fs::copy, which truncates and rewrites the same inode under the running compiler. It now copies the new build to a sibling temporary and renames it over the name.

before: reinstall while a kernel compiles
        rustc killed 5/5 (SIGSEGV, even with identical bytes); an mmap reader SIGBUS 6/6
after:  rustc finishes 5/5; the reader survives 6/6
        cache: librustc_codegen_cuda.so (new inode, mode 0755), two records, no .tmp
// install_backend_into, the one path every cache writer takes
std::fs::copy(built_so, &temporary)?;   // librustc_codegen_cuda.so.<pid>.<nonce>.tmp
record_source_rev(cache_dir, None);      // "no record" counts as stale
std::fs::rename(&temporary, &so_path)?;  // same directory: one atomic rename(2)

Installing the cached backend by rename

What we fixed

  • A running rustc no longer dies when the backend is reinstalled. Running compilers keep the old inode until they exit, and one that starts during the install opens a complete file.
  • A failed copy keeps the old backend and its commit record. A failed rename removes the temporary and returns the error unchanged.
  • cargo oxide update removes a temporary left by an install killed between copy and rename. Installs never sweep, since they cannot tell a stranded temporary from another installer's live one.
Implementation
  • install_backend_into is the single chokepoint: build_and_cache, auto_fetch_and_build and publish_to_cache (cargo oxide setup) all go through it. In-repo builds take discovery step 3 and never touch the cache.
  • The temporary lives in the cache directory, so the rename is a same-filesystem rename(2). The nonce comes from std's RandomState, no new dependency. fs::copy sets the source's mode with fchmod, so umask cannot drop 0o755.
  • The commit record is cleared just before the rename and rewritten after it, so the only intermediate state is "no record", which cached_backend_status treats as stale.
  • Tests: an open handle keeps reading the old bytes on a new inode; a failed copy keeps bytes and record; a failed rename removes the temporary; update removes a planted temporary while an install and the automatic invalidation leave it alone.

Verification

Checked at 4b4558e74 (nightly-2026-10-11, RTX 5090):

  • ✅ cargo test -p cargo-oxide --all-targets: 260 passed. Clippy and rustdoc with warnings as errors, fmt, the 18 repository guards, the example lock check and cargo deny: clean.
  • ✅ Fails before, passes after: with the old fs::copy body, installing_leaves_a_loaded_backend_intact panics (left: "new backend") and a reader that maps a 64 MB install is killed by SIGBUS 6/6; with this body it survives 6/6.
  • ✅ A real rustc with the 428 MB backend mapped, replaced while it ran: SIGSEGV 5/5 in place, exit 0 5/5 by rename.
  • ✅ 8 threads x 10 concurrent installs into one cache: one complete file, no temporary left.
  • ✅ cargo oxide run vecadd through this branch's cargo-oxide binary: SUCCESS, memcheck clean.

Caveats

  • The two record files beside the .so are still written in place and are not atomic with the install, so two projects at different commits racing on one cache can pair one project's record with the other's backend. The window is now a rename plus two small writes. A lock file or a per-commit layout is a separate change.
  • Windows: a loaded DLL cannot be renamed over, as it could not be overwritten before (Windows MSVC support fork and signed release artifacts #139).

@midagedev
midagedev force-pushed the fix/atomic-backend-install branch from cada00a to f4a5ec9 Compare September 25, 2026 00:13
install_backend_into copied the new backend over the cached .so with
fs::copy. That truncates and rewrites the same inode, so a rustc that
already loaded the old file can die with SIGBUS.

Copy to a temporary file in the cache dir, then rename it over the .so.
If the copy or the rename fails, remove the temporary file and return
the error.

Tests: an open handle keeps reading the old bytes and inode after an
install; a failed copy or rename leaves the old .so and no temporary
file.

Signed-off-by: midagedev <midagedev@gmail.com>
@midagedev
midagedev force-pushed the fix/atomic-backend-install branch from f4a5ec9 to 95d1174 Compare October 8, 2026 02:44
@midagedev
midagedev requested a review from nihalpasham as a code owner October 8, 2026 02:44
@copy-pr-bot

copy-pr-bot Bot commented Oct 8, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@nihalpasham nihalpasham added the cuda-oxide SIMT programming model: rustc backend, cargo oxide, cuda-device, cuda-host, book label Oct 11, 2026
install_backend_into removed source-rev.txt before copying the new
backend to its temporary. The copy no longer touches the cached .so, so
a copy that fails, for example on a full disk, left the old backend in
place without its record, and the next pinned lookup rebuilt a cache
that was still valid. The record is now removed between the copy and
the rename, so the only state without a record is the short window from
the rename to the new record, and a failed copy costs nothing.

The failed-copy test installs under a commit and checks that the record
survives the failed second install; it fails with the old ordering.

Signed-off-by: nihalpasham <nihalp@nvidia.com>
An install killed between its copy and its rename leaves
librustc_codegen_cuda.so.<pid>.<nonce>.tmp in the cache directory, as
large as the backend, and nothing removed it. cargo oxide update now
removes such temporaries after clearing the cache. The sweep runs only
on that explicit path: clear_cache_contents doubles as the automatic
invalidation any project's lookup can run, and an install cannot tell a
stranded temporary from another installer's live one, so neither of
them removes temporaries.

is_backend_temporary sits beside temporary_backend_path so the name
policy lives in one place. Tests cover the sweep, an install that
leaves a planted temporary alone, and the automatic path that leaves
it alone.

Signed-off-by: nihalpasham <nihalp@nvidia.com>
installing_leaves_a_loaded_backend_intact listed the cache directory
while its handle to the replaced backend was still open. A network
filesystem keeps a replaced file under a hidden name until the last
handle closes, which would fail the "no temporary left" assertion on
such a mount. The handle is closed first, so the assertion depends
only on the install.

Signed-off-by: nihalpasham <nihalp@nvidia.com>
A one-page figure for the book and the pull request, in the style of the
tile-libraries direct-linkage figure.

Signed-off-by: nihalpasham <nihalp@nvidia.com>
@nihalpasham nihalpasham added bug Something isn't working cargo-oxide The `cargo oxide` subcommand and build automation build-related Build scripts, toolchain discovery, or compilation setup labels Oct 19, 2026
@nihalpasham

Copy link
Copy Markdown
Collaborator

/ok to test 4b4558e

@nihalpasham

Copy link
Copy Markdown
Collaborator

Thanks, this is the right fix: the cached backend is the one file in the cache that other processes map, and a sibling temporary plus rename is what cargo-oxide already does for its other installs. Three things on top:

  • Moved the commit-record clear to just before the rename, so a failed copy keeps both the old backend and its record.
  • cargo oxide update now removes a .tmp left by an install killed between copy and rename. Installs never sweep: they cannot tell a stranded temporary from another installer's live one.
  • Merged main so CI ran on the nightly-2026-10-11 pin, and added a one-page figure to the installation chapter.

At 4b4558e74, 260 cargo-oxide tests, clippy, rustdoc, the guards, cargo deny and all CI checks passed. A real rustc with the 428 MB backend mapped died 5/5 under the old installer and finished 5/5 under this one. The record race between two projects on one cache is pre-existing and stays open.

@nihalpasham
nihalpasham merged commit 937de64 into NVIDIA:main Oct 19, 2026
64 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working build-related Build scripts, toolchain discovery, or compilation setup cargo-oxide The `cargo oxide` subcommand and build automation cuda-oxide SIMT programming model: rustc backend, cargo oxide, cuda-device, cuda-host, book

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants