filesystem: retry transiently refused FUSE invalidations with backoff
Closed
VA
1d ago+228+228+012345678901234567890123456789-23-23-01234567890123456789
Follow-up to the P1 from the #564 review (invalidation.rs:295). Before this change, a transient kernel refusal stopped retries until another edit, control call or second waiter came along.
Change
run_invalidatortreatsInterruptedandWouldBlockrefusals as transient. For those it sets a bounded exponential backoff: 1 ms, doubling, capped at 1 s.- While a backoff is pending, it waits on the projection condition with
wait_timeout. Any other change still wakes it immediately, and the backoff expiring retries the queued item on its own. - Non-transient refusals behave as before (the #564 fix): the item stays queued and the invalidator waits for a retry request or new input.
- The projection
stateMutex andinvalidationCondvar go through aliases (StateMutex,StateCondvar,StateMutexGuard). Undercfg(acyclic_loom)these swap in loom's types; otherwise they are std's.cfg(acyclic_loom)is registered incheck-cfg.
Model
native_mount/fuse/tests/model.rs is a loom model of the real run_invalidator against a real projection_core:
- Kernel: refuses notifications with EINTR in every one of the 16 patterns over the first four it receives.
- Main thread: queues two inode invalidations, then waits for them to settle the way a mount's reader does, without requesting a retry.
- Bound: preemption bound 2.
- Assertion: every queued notification reaches the kernel, in order.
Run it with:
RUSTFLAGS="--cfg acyclic_loom" cargo test -p acyclic-fs --all-features --lib native_mount::fuse::tests::model
Verification (bb1)
- Model with the fix: passes, 1.13 s and 1.15 s on two runs.
- Model with the fix reverted (backoff-elapsed check removed): fails on both runs, with loom reporting
deadlock; threads = [... Blocked ...]within 0.02 s. - std FUSE tests: all pass except
filtered_callback_error_replies_without_overwriting_parent_errno, which also fails on main. - clippy (std and
acyclic_loom,--no-deps): no findings in touched files. The other findings in native-runtime and acyclic-fs are already on main. - rustfmt: clean on touched files.
Unverified
- Full
cargo clippy -D warningsacross the workspace (red on main for unrelated reasons).
🤖 Generated with Claude Code
Summary by cubic
Fixes indefinite blocking when the kernel transiently refuses FUSE cache invalidations with EINTR or EWOULDBLOCK.
- The invalidator now retries on its own with exponential backoff (1 ms doubling to 1 s) after a transient refusal, and any other change still wakes it immediately.
- Non-transient refusals keep the existing behavior: the item stays queued until a retry is requested or inputs change.
- Adds a loom model that runs the real invalidator against a kernel refusing every pattern of its first four notifications, asserting each queued notification reaches the kernel in order.