fix(fuse): complete open handle before unlinking path#966
Open
ljy1-lixing wants to merge 1 commit into
Open
Conversation
## Summary Fix a FUSE unlink race where a path could remain visible when it was unlinked immediately after closing an open file. When a file was closed and then unlinked right away, the kernel could send `unlink` before the FUSE `release` for the closed handle had finished. Before this change, Curvine saw that the file still had an open handle and deferred backend deletion. That left the old backend path visible temporarily. A subsequent open with create semantics for the same name could then resolve the old file during lookup instead of creating a new file, causing intermittent open failures or stale-path behavior. ## Root Cause Curvine's unlink path treated an inode with open handles as a delayed-delete case. That preserved the backend path until the final handle was released. This is unsafe for same-name recreation because POSIX unlink semantics require the directory entry to disappear when `unlink()` returns. Existing open handles may continue to complete their own file state, but they must not keep the unlinked pathname visible or block a new file with the same name. ## Changes - In FUSE `unlink`, detect an existing open handle for the target path. - Complete the open handle before deleting the backend path. - Delete the backend path immediately after the handle is completed. - Expose `NodeState::find_open_handle_by_path()` so the unlink path can find the active handle by pathname. - Remove temporary debug logging used during diagnosis. ## Verification - Repeated same-name close, unlink, and reopen/create workflow passes after the fix. - `cargo fmt --check` passes. - `cargo test -p curvine-fuse ...` could not run locally because `protoc` is not installed.
szbr9486
reviewed
Jul 2, 2026
| if self.state.should_delete_now(parent_ino, Some(name))? { | ||
| let completed_open_handle = if let Some(handle) = self.state.find_open_handle_by_path(&path) | ||
| { | ||
| handle.complete(None).await?; |
Contributor
There was a problem hiding this comment.
This completes the matched open handle during unlink, but unlink can run while the file descriptor is still genuinely open and may still receive writes, flushes, or reads before release. Completing the backend stream here can commit/close the writer early, so later operations on the same fd may fail or write after completion. Please avoid completing arbitrary open handles from unlink; the same-name recreation fix needs to remove the directory entry without changing the lifecycle of still-open fds, or only act on handles that are known to already be in the release/closed path.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fix a FUSE unlink race where a path could remain visible when it was unlinked immediately after closing an open file.
When a file was closed and then unlinked right away, the kernel could send
unlinkbefore the FUSEreleasefor the closed handle had finished. Before this change, Curvine saw that the file still had an open handle and deferred backend deletion. That left the old backend path visible temporarily.A subsequent open with create semantics for the same name could then resolve the old file during lookup instead of creating a new file, causing intermittent open failures or stale-path behavior.
Root Cause
Curvine's unlink path treated an inode with open handles as a delayed-delete case. That preserved the backend path until the final handle was released.
This is unsafe for same-name recreation because POSIX unlink semantics require the directory entry to disappear when
unlink()returns. Existing open handles may continue to complete their own file state, but they must not keep the unlinked pathname visible or block a new file with the same name.Changes
unlink, detect an existing open handle for the target path.NodeState::find_open_handle_by_path()so the unlink path can find the active handle by pathname.Verification