Skip to content

Unmount lazily on Linux, so a busy filesystem is not left mounted - #714

Merged
cberner merged 1 commit into
masterfrom
claude/fuser-issues-triage-7tz8v9
Jul 31, 2026
Merged

Unmount lazily on Linux, so a busy filesystem is not left mounted#714
cberner merged 1 commit into
masterfrom
claude/fuser-issues-triage-7tz8v9

Conversation

@cberner

@cberner cberner commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Fixes #686.

An unprivileged unmount has always been lazy, going through fusermount -u -z, but a privileged one called umount(2) with no flags, so it failed with EBUSY while the filesystem was still in use. By then the Mount has been consumed, so the caller has nothing left to retry with and the filesystem stays mounted.

This detaches lazily on Linux instead, which is what libfuse's fuse_kern_unmount() does in both cases. libc_umount() is shared, so this covers all three backends.

Behavior, with one open handle on the mount root

root, before unprivileged, before and after root, after
SessionUnmounter::unmount() EBUSY, stays mounted Ok, lazily detached Ok, lazily detached
umount_and_join() EBUSY, stays mounted blocks until handles are released blocks until handles are released
drop(BackgroundSession) warns, stays mounted waits UNMOUNT_WAIT, then detaches waits UNMOUNT_WAIT, then detaches

So this is not purely a fix: privileged join()/umount_and_join() callers trade an immediate EBUSY for waiting on open handles. That seemed worth it, since the alternative leaves a filesystem mounted with no way to retry, and it is what the unprivileged path has always done. Teardown was already built for lazy semantics: BackgroundSession::drop waits a bounded time for the connection to end and detaches the session thread if it does not.

The retry of the same unmount syscall in fuse_unmount_pure() is dropped. It is only reached once umount(2) has failed with EPERM, which different flags do not change.

Testing

umount_succeeds_while_filesystem_is_busy holds an open handle on the mount root and asserts the unmount does not fail. It fails with EBUSY on master when run as root, and passes with this change. Note that CI runs the unit tests unprivileged, where it passes either way — it only guards the regression when the suite is run as root, which is how I verified it.

Also run locally, on all three backends both as root and unprivileged: the unit suite, cargo fmt --check, clippy with and without default features, the musl build, cargo doc, cargo check --target x86_64-apple-darwin --features=macos-no-mount, and both passthrough example tests.

Deliberately not included

The issue also asks for EBUSY handling on macOS and the BSDs, where there is no lazy unmount. I left both alone: neither is reachable by CI here (mac-ci is build/lint only, and freebsd-ci runs its cargo test step unprivileged and skips the mount tests), so a retry loop or MNT_FORCE would ship unverified. MNT_FORCE in particular is a much stronger semantic than a lazy detach — it revokes open handles, so in-flight I/O fails and unflushed data can be lost. Happy to take either on if you want it here.

Two points from the issue's follow-up comment are already resolved on master: unprivileged unmount is no longer gated away for libfuse2 on Linux (fuse2.rs calls fusermount_unmount()), and Drop for MountImpl now calls fuse_session_destroy() on every path.


Generated by Claude Code

An unprivileged unmount has always been lazy, going through "fusermount -u -z",
but a privileged one called umount(2) with no flags and so failed with EBUSY
while the filesystem was still in use. By then the Mount has been consumed, so
the caller has nothing left to retry with and the filesystem stays mounted.

Detach lazily on Linux instead, which is what libfuse's fuse_kern_unmount() does
in both cases. Teardown is already built for it: BackgroundSession::drop waits a
bounded time for the connection to end and detaches the session thread if it does
not. The privileged path now behaves as the unprivileged one always has, which
includes join() and umount_and_join() waiting for open handles to be released
rather than reporting EBUSY.

The retry of the same unmount syscall in fuse_unmount_pure() is dropped: it is
only reached once umount(2) has failed with EPERM, which different flags do not
change.

Fixes #686

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NjYWzn3mu8Sc2y2eACtJM9

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cedf2411e0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/mnt/mod.rs
@cberner
cberner merged commit 2c6beff into master Jul 31, 2026
9 checks passed
@cberner
cberner deleted the claude/fuser-issues-triage-7tz8v9 branch July 31, 2026 22:49
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.

Inconsistent unmount flags which leads to different behaviors

2 participants