DioxusLabs / DioxusLabs/dioxus
cli: interrupted write of libdeps-<hash>.a permanently poisons the fat-binary cache (written in place, cache hit accepts empty archives)
- Dominant language
- Rust
- Stars
- 39.1k
- Forks
- 1.9k
- Avg merge
- 4d 10h
- Merged PRs (30d)
- 4
Description
## Problem
When `dx` is killed while writing the fat-binary deps archive `libdeps-.a` (Ctrl+C, SIGTERM/SIGKILL, a supervisor restarting it, machine crash…), it leaves an empty or truncated archive behind — and every subsequent build then fails forever with:
```
ERROR Failed to generate fat binary: ld: file is empty in
'~/.cargo/target/aarch64-apple-darwin/desktop-dev/libdeps-2241ac6f.a'
clang: error: linker command failed with exit code 1
ERROR Build failed: No such file or directory (os error 2)
```
The only way out is to manually delete the profile dir (e.g. `rm -rf //desktop-dev`). Nothing in dx ever detects or repairs the poisoned cache.
## Root cause
Two things combine in `packages/cli/src/build/link.rs` (still present on current `main`, verified today):
1. **The archive is written in place — no temp file + atomic rename** (link.rs:1000-1001):
```rust
let bytes = out_ar.into_inner().context("Failed to finalize archive")?;
std::fs::write(&out_ar_path, bytes).context("Failed to write archive")?;
```
`std::fs::write` truncates then writes at the final path, so dying mid-write leaves an empty/partial `libdeps-.a` at the cached location.
2. **The cache-hit check only tests existence — no content validation** (link.rs:923-941):
```rust
let mut archive_has_contents = out_ar_path.exists();
...
if !archive_has_contents || cfg!(debug_assertions) {
```
On a release build of dx, `cfg!(debug_assertions)` is false, so any pre-existing file — including a zero-byte one — is accepted as a valid cache hit and fed straight to the linker.
## Reproduction (dx 0.7.3, also applies to current main)
1. Run `dx serve` (or any hot-patch workflow) on a project with many dependencies.
2. While the fat binary is being generated (first compile), `kill -9` the dx process (touching `Cargo.toml` at the wrong moment does it too if a wrapper restarts dx on manifest changes).
3. Relaunch → `ld: file is empty in .../libdeps-.a` on every build until the file is manually deleted.
We hit this repeatedly in a wrapper tool that restarts dx when `Cargo.toml` changes: a version bump triggered the restart exactly during the post-bump archive regeneration, and every developer build was broken until a manual purge.
## Suggested fix
Either (ideally both):
- Write the archive to a temp path (`libdeps-.a.tmp`) and atomically `rename` it into place, so a partial write is never visible at the cached path.
- Validate the archive on cache hit before using it: size > 0 and the `!` ar magic; regenerate on mismatch.
Both are cheap and would make the cache self-healing after any interruption.
Environment: dx 0.7.3 (macOS aarch64), behavior confirmed unchanged in released 0.7.9 and on current `main`.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with packages/cli/src/build/link.rs, especially the cache-hit logic around lines 923–941 and archive write around lines 1000–1001. Run the described dx serve interruption and restart reproduction; done means interrupted writes cannot poison the final cache path, invalid or empty archives are rejected and regenerated, and the workflow recovers without manually deleting the profile directory.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- build-system, cli
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100