Skip to content

fix(cli): retry exec of downloaded binary on ETXTBSY - #47

Closed
knihit wants to merge 2 commits into
mainfrom
fix/self-update-smoke-etxtbsy
Closed

knihit wants to merge 2 commits into
mainfrom
fix/self-update-smoke-etxtbsy

Conversation

@knihit

@knihit knihit commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Description

CLI:

  • cli_self_update.rs: update --cli's smoke test now retries the exec of the freshly downloaded binary on ExecutableFileBusy (os error 26), instead of failing the update outright.

On Linux, fork() duplicates every file descriptor open anywhere in the process. If another thread forks while the downloaded binary's write handle is still open, the child inherits that handle and holds it until it execs or exits. Until then, exec of the binary fails with "Text file busy", even though the smoke test's own write handle closed well before the exec was attempted. This is a known class of race in the Rust standard library; see rust-lang/rust#114554.

The fix retries the spawn up to 10 times, 100ms apart (about 1 second total), but only when the failure is ExecutableFileBusy or raw os error 26. The retry loop lives in its own function with an injectable spawn/sleep seam, so a unit test can drive it deterministically with a fake exec that fails a set number of times then succeeds, without depending on reproducing the real race.

  • Three new unit tests cover the retry: succeeds after a few failures, exhausts all 10 attempts and returns the error, and leaves a non-busy error untouched.
  • Version bumped 1.0.3 -> 1.0.5 with a corresponding CHANGELOG.md entry.

Fixes # (issue)

Type of Change

  • CLI (cli/) change — Rust

Testing

  • If cli/ changed: cd cli && make test passes (Rust + Python + conformance suites)
  • New or changed code files carry the required SPDX header (see AGENTS.md's License headers rule); formats without comment syntax (e.g. JSON) are exempt

Full cargo test passes locally: 1725 tests. Ran the full suite 15 times in a row at 64 threads to rule out flakiness from the timing-sensitive retry logic; all 15 runs were green.

Checklist

  • My code follows the style guidelines of this project (see AGENTS.md's Code Style & Conventions)
  • I have performed a self-review of my own changes
  • I have commented my code where necessary
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings — build is clean, and for cli/ changes, cd cli && make lint passes (cargo clippy -- -D warnings + cargo fmt --check)
  • I have added or updated tests that prove my change works (see Testing above for the exact commands)

Agents Affected

None.

Notes for Reviewers

This only affects the self-update path on Linux, where it's possible for a concurrent fork elsewhere in the process to transiently hold the downloaded binary's file descriptor open. The retry window (about 1 second) is a judgment call: long enough to clear the race in practice, short enough that a genuinely broken binary still fails the update promptly.

update --cli's smoke test spawns the freshly downloaded binary with
Command::output(). On Linux, fork() duplicates every file descriptor
open anywhere in the process, so an unrelated Command::spawn() running
concurrently elsewhere in the same process can fork a child that
inherits a transient write handle on the just-written binary and hold
it open until that child execs or exits. Until then, exec of the
binary fails with ETXTBSY (os error 26) even though the smoke test's
own write handle closed well before the exec was attempted.

Retry the spawn up to 10 times with a 100ms delay between attempts
(about 1 second total) when the failure is ExecutableFileBusy, giving
the other process time to exec or exit and release its inherited
handle. The retry loop is extracted into its own function with an
injectable spawn/sleep seam, so a unit test can drive it
deterministically with a fake exec that fails N times then succeeds,
and a separate case that exhausts all retries, without depending on
reproducing the real race.
@knihit

knihit commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

The retry fix is now in #62, without the version bump. #62 also stops skipping the smoke test in CI.

@knihit knihit closed this Oct 16, 2026
@knihit
knihit deleted the fix/self-update-smoke-etxtbsy branch October 16, 2026 19:36
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.

1 participant