Skip to content

fix(daemon): integrate stalled unmount recovery with exact abort custody (#496) - #500

Merged
yifanxuaaa merged 2 commits into
mainfrom
fix/pr496-abort-custody
Oct 28, 2026
Merged

yifanxuaaa merged 2 commits into
mainfrom
fix/pr496-abort-custody

Conversation

@yifanxuaaa

@yifanxuaaa yifanxuaaa commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Integrates #496 and fixes its retained abort evidence before merging. A normal unmount that stalls at Join still makes one abort write and drains again; unsuccessful teardown now preserves the actual abort disposition and original errno/count through the second drain, Revoke and Close. Successful normal unmounts still return Unmounted.

The branch contains #496's original commit plus 69fedcacf0. GitHub rejected the maintainer push to the author's fork, so this upstream branch carries the reviewed correction without rewriting the author's history. Merge with a merge commit so #496's original head remains in main's ancestry.

Validation on Linux ARM64 with Rust 1.85.1, locked dependencies and repository flags:

  • Parent regression: failed/short abort tests fail with Join/forced=None; corrected stalled-unmount suite: 6 passed.
  • Existing forced_unmount: 3 passed; forced_unavailable: 1 passed; fp21_detach_stages: 2 passed.
  • FUSE/Bridge all-target tests: 33 passed.
  • All-target Clippy for daemon/FUSE/Bridge with -D warnings passed; Rust formatting, product boundary, authorized fuser integrity, and Ruff 0.17.2 formatting passed.
  • 155 tooling tests passed; CI selection's six tests passed again after recording the expanded suite in the slow inventory while retaining its mount lane.
  • Tests compile first and have explicit execution timeouts (100 s mount, 90 s FUSE/Bridge, 120 s tooling). Disposable only; Durable remains disabled. No SDK-wide E2E or performance qualification.

Failure tests use thread-local seccomp on the test's own abort descriptor, including an externally simulated acknowledged write to exercise the second drain stop. The Revoke/Close fixtures use public owner APIs. No production test hooks, new dependencies or wire format changes. No deleted tests.

Combined merge tree ddea2b5453ec2294a3a3096ff1341d29467c9ff5 against main 9af53da046: all six stalled-unmount cases and all eight SDK unmount-dirty tests pass.

Details, failed fixture diagnosis and evidence locations: fix record.

Production LOC, corrective commit: 102777 -> 102816 (delta +39): core 0, engine +39, API 0.
Prepared first-parent merge comparison against main 9af53da0468c2ea7b3a273a4a7037afc059ff27b: 102778 -> 102831 (delta +53); core 38395 unchanged, engine 61724 -> 61777, API 2659 unchanged. Counter: tools/production_loc.py blob 3341e4d0362c822b6d9a08419c432942b3a13800, --root <exact exported tree> --json, excluding tests/docs/tools/generated/third-party code. Reference absent (retired R9c; last counted 65,417 at tree 498dd1917812).

mengmengmengqiang and others added 2 commits October 28, 2026 22:08
…write (#426, #460)

A normal unmount can detach successfully and still stop at Join: when
another mount of the same superblock outlives the detach, the kernel never
ends the connection, its receive loops stay blocked, and the entry was kept
as Retained at Join with no way out but a forced unmount — or, where no
abort control is bound, no way out at all (#426, #460).

The unmount now makes the same single abort write a forced unmount makes,
on the already-detached connection, and drains again. It then completes as
Unmounted, revokes and closes, so the stalled connection ends where the
unmount asked. Only a refused or short write, or a second stop, keeps the
custody, exactly as any other stop does; such an entry is not forceable
either, because its abort control has been used.

- layerfs-fuse: Undrained::end_stalled, one abort_detached write then
  force_drain, only for a Join stop.
- layerfs-daemon: unmount_kernel calls it on a Join stop; a second stop
  keeps the evidence as before.
- tests: stalled_unmount now asserts the normal unmount ends the stall
  itself (the copy of the mount answers ENOENT) and keeps no custody.
- docs: native mount session and forced teardown describe the automatic
  abort; the SDK skill and the unmount comment no longer send callers to
  --force for this case.

Tests:
- cargo test -p layerfs-daemon --test stalled_unmount --release in a
  privileged Linux container: passes; the parent's code keeps custody, so
  the test fails without this change.
- also ran mounted_drain, fp21_detach_stages, control_root, mounted_fusectl,
  forced_unmount and forced_unavailable; all pass except the environment
  bound fp31 reclaim observation in mounted_drain (container cgroup memory
  reclaim, unrelated to unmount).

Production LOC: 102763 -> 102777 (delta +14)
  core 38395 -> 38395 (0), engine 61709 -> 61723 (+14), api 2659 (0).
Record the attempted abort in the FUSE failure receipt. Report failed and
short writes at Abort with the original errno/count, and carry abort effects
through second-drain, Revoke and Close failures. Successful normal unmounts
still reply Unmounted, with no new syscall, replay or ownership release.

Add five Linux mounted regressions alongside the success case using external
thread-local seccomp and public owner APIs. Parent: failed/short abort tests
fail with Join/forced=None. Fixed: all six pass; existing forced_unmount (3),
forced_unavailable (1), fp21_detach_stages (2), and FUSE/Bridge tests (33) pass.
All-target Clippy for daemon/FUSE/Bridge denies warnings; fmt, product and
fuser guards pass. Tooling: 155 tests plus six after CI inventory adjustment.
Tests built first; execution capped at 100 s (mount), 90 s (FUSE/Bridge),
120 s (tooling). Disposable only. No full SDK E2E or benchmark claim.
The final strengthened success assertion passes and its Clippy check passes.
Ruff 0.17.2 formatting passes; optional lint retains existing EXE001 on the
non-executable ci_tests.py shebang. No third-party changes. Deleted tests: none.

Production LOC: 102777 -> 102816 (delta +39)
First-parent comparison: 3a0564b
(tree 799fab1) -> staged tree 7f0d78d.
Scopes: core 38395 -> 38395 (0); engine 61723 -> 61762 (+39);
api 2659 -> 2659 (0).
Method: tools/production_loc.py blob 3341e4d,
--root <exact exported tree> --json for each snapshot. First-party production
Rust, runtime SQL and JavaScript; tests, docs, tooling, generated and
third-party code excluded.
Reference absent (retired R9c; last counted 65,417 at tree 498dd19).

Evidence and limitations: core/docs/issues/496/ABORT-CUSTODY-FIX-20261029.md.
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.

2 participants