mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-17 19:48:35 +02:00
* mount: propagate request context through metadata lookups The FUSE interrupt channel never reached the filer RPC: lookupEntry ran its LookupEntry under context.Background(), so a stalled filer parked ops like ListXAttr forever even after the kernel gave up on them. Thread a context from each op's cancel channel through maybeReadEntry, maybeLoadEntry, lookupEntry, ensureDirectoryVisited and waitForPendingAsyncFlush; the existing withFilerClient attempt bound turns it into both an interrupt and a deadline. A caller cancellation now surfaces as EINTR instead of waiting out the RPC. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * mount: reuse ctxFromCancel for the read path's interrupt bridge Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * mount: regression tests for canceled and stalled filer lookups Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * mount: thread ctx through AcquireHandle Open, Create and Mknod all carry the request's cancel channel; let it bound the pending-flush wait and metadata load inside AcquireHandle too. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * mount: keep flush waits and write sequences uninterruptible The cancel-derived context must only cover metadata reads: an interrupted Unlink or Rename that skips the pending-flush wait can race the async write back into existence, and Link/Rename mutations that run under a canceled context abort mid-sequence, strand the rollback, and leave wrong hard-link counts. Restore the blocking flush wait and run each op's write phase on context.Background(). Rmdir and Unlink also propagate EINTR from the parent lookup instead of proceeding past a permission check the interrupt prevented. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * mount: interrupt shared directory builds and directory reads Waiters behind another request's singleflight directory build blocked until it finished even when interrupted; switch the wait to DoChan so the request context can abandon it. ReadDir paths now carry the request context through EnsureListingFresh, cache listings and direct reads, and map its cancellation to EINTR rather than EIO. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * mount: assert exact statuses in filer-stall regression tests Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * mount: let read-side flush waits release on interrupt waitForPendingAsyncFlush reports whether the flush actually finished: lookupEntry and AcquireHandle return EINTR when the request is abandoned mid-wait, while Unlink and Rename keep waiting on context.Background() so the delete/rename still sees the flushed state. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * mount: propagate interrupt through remaining lookup checks Rename's source and destination permission lookups now return EINTR on interrupt instead of skipping checkStickyBit or masking an existing target, and the readdir preload visit maps cancellation to EINTR like the other branches. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * mount: fail closed on errored rename prerequisite lookups * mount: stop rmdir on interrupted target lookup, fail closed on dir lookup errors --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
161 lines
5.8 KiB
Go
161 lines
5.8 KiB
Go
package mount
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/seaweedfs/go-fuse/v2/fuse"
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
)
|
|
|
|
/**
|
|
* Open a file
|
|
*
|
|
* Open flags are available in fi->flags. The following rules
|
|
* apply.
|
|
*
|
|
* - Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be
|
|
* filtered out / handled by the kernel.
|
|
*
|
|
* - Access modes (O_RDONLY, O_WRONLY, O_RDWR) should be used
|
|
* by the filesystem to check if the operation is
|
|
* permitted. If the ``-o default_permissions`` mount
|
|
* option is given, this check is already done by the
|
|
* kernel before calling open() and may thus be omitted by
|
|
* the filesystem.
|
|
*
|
|
* - When writeback caching is enabled, the kernel may send
|
|
* read requests even for files opened with O_WRONLY. The
|
|
* filesystem should be prepared to handle this.
|
|
*
|
|
* - When writeback caching is disabled, the filesystem is
|
|
* expected to properly handle the O_APPEND flag and ensure
|
|
* that each write is appending to the end of the file.
|
|
*
|
|
* - When writeback caching is enabled, the kernel will
|
|
* handle O_APPEND. However, unless all changes to the file
|
|
* come through the kernel this will not work reliably. The
|
|
* filesystem should thus either ignore the O_APPEND flag
|
|
* (and let the kernel handle it), or return an error
|
|
* (indicating that reliably O_APPEND is not available).
|
|
*
|
|
* Filesystem may store an arbitrary file handle (pointer,
|
|
* index, etc) in fi->fh, and use this in other all other file
|
|
* operations (read, write, flush, release, fsync).
|
|
*
|
|
* Filesystem may also implement stateless file I/O and not store
|
|
* anything in fi->fh.
|
|
*
|
|
* There are also some flags (direct_io, keep_cache) which the
|
|
* filesystem may set in fi, to change the way the file is opened.
|
|
* See fuse_file_info structure in <fuse_common.h> for more details.
|
|
*
|
|
* If this request is answered with an error code of ENOSYS
|
|
* and FUSE_CAP_NO_OPEN_SUPPORT is set in
|
|
* `fuse_conn_info.capable`, this is treated as success and
|
|
* future calls to open and release will also succeed without being
|
|
* sent to the filesystem process.
|
|
*
|
|
* Valid replies:
|
|
* fuse_reply_open
|
|
* fuse_reply_err
|
|
*
|
|
* @param req request handle
|
|
* @param ino the inode number
|
|
* @param fi file information
|
|
*/
|
|
func (wfs *WFS) Open(cancel <-chan struct{}, in *fuse.OpenIn, out *fuse.OpenOut) (status fuse.Status) {
|
|
var fileHandle *FileHandle
|
|
ctx, cancelFunc := ctxFromCancel(cancel)
|
|
defer cancelFunc()
|
|
fileHandle, status = wfs.AcquireHandle(ctx, in.NodeId, in.Flags, in.Uid, in.Gid)
|
|
if status == fuse.OK {
|
|
out.Fh = uint64(fileHandle.fh)
|
|
out.OpenFlags = 0
|
|
|
|
// For read-only opens, set FOPEN_KEEP_CACHE when the file's mtime
|
|
// has not changed since the last open. This tells the kernel to
|
|
// preserve its existing page cache, avoiding redundant reads.
|
|
if in.Flags&fuse.O_ANYWRITE == 0 {
|
|
if entry := fileHandle.GetEntry(); entry != nil && entry.Attributes != nil {
|
|
wfs.applyKeepCacheFlag(in.NodeId, entry, out)
|
|
}
|
|
}
|
|
}
|
|
return status
|
|
}
|
|
|
|
/**
|
|
* Release an open file
|
|
*
|
|
* Release is called when there are no more references to an open
|
|
* file: all file descriptors are closed and all memory mappings
|
|
* are unmapped.
|
|
*
|
|
* For every open call there will be exactly one release call (unless
|
|
* the filesystem is force-unmounted).
|
|
*
|
|
* The filesystem may reply with an error, but error values are
|
|
* not returned to close() or munmap() which triggered the
|
|
* release.
|
|
*
|
|
* fi->fh will contain the value set by the open method, or will
|
|
* be undefined if the open method didn't set any value.
|
|
* fi->flags will contain the same flags as for open.
|
|
*
|
|
* Valid replies:
|
|
* fuse_reply_err
|
|
*
|
|
* @param req request handle
|
|
* @param ino the inode number
|
|
* @param fi file information
|
|
*/
|
|
const openMtimeCacheMaxSize = 8192
|
|
|
|
// applyKeepCacheFlag compares the entry's mtime (seconds + nanoseconds) against
|
|
// the last-seen value and sets FOPEN_KEEP_CACHE when unchanged.
|
|
func (wfs *WFS) applyKeepCacheFlag(inode uint64, entry *LockedEntry, out *fuse.OpenOut) {
|
|
currentMtime := [2]int64{entry.Attributes.Mtime, int64(entry.Attributes.MtimeNs)}
|
|
wfs.openMtimeMu.Lock()
|
|
prev, loaded := wfs.openMtimeCache[inode]
|
|
if loaded && prev == currentMtime {
|
|
out.OpenFlags |= fuse.FOPEN_KEEP_CACHE
|
|
} else {
|
|
if len(wfs.openMtimeCache) >= openMtimeCacheMaxSize {
|
|
for k := range wfs.openMtimeCache {
|
|
delete(wfs.openMtimeCache, k)
|
|
break
|
|
}
|
|
}
|
|
wfs.openMtimeCache[inode] = currentMtime
|
|
}
|
|
wfs.openMtimeMu.Unlock()
|
|
}
|
|
|
|
// invalidateOpenMtimeCache removes an inode's cached mtime so the next Open
|
|
// does not set FOPEN_KEEP_CACHE with stale kernel page cache data.
|
|
func (wfs *WFS) invalidateOpenMtimeCache(inode uint64) {
|
|
wfs.openMtimeMu.Lock()
|
|
delete(wfs.openMtimeCache, inode)
|
|
wfs.openMtimeMu.Unlock()
|
|
}
|
|
|
|
func (wfs *WFS) Release(cancel <-chan struct{}, in *fuse.ReleaseIn) {
|
|
// Flush is usually sent before Release, but the FUSE protocol does not
|
|
// guarantee it. Route every Release through doFlush so a dirty handle
|
|
// (e.g. a deferred create with no intervening Flush) is not dropped.
|
|
// doFlush itself inspects dirtyMetadata / asyncFlushPending and fast-paths
|
|
// the clean case, so the duplicate call after a normal Flush is cheap.
|
|
if fh := wfs.GetHandle(FileHandleId(in.Fh)); fh != nil {
|
|
allowAsync := in.ReleaseFlags&fuse.FUSE_RELEASE_FLOCK_UNLOCK == 0
|
|
// Release is the last chance to persist the handle, so it must finish
|
|
// even if the triggering syscall was interrupted: non-cancellable context.
|
|
if status := wfs.doFlush(context.Background(), fh, in.Uid, in.Gid, allowAsync); status != fuse.OK {
|
|
glog.Warningf("release fh %d inode %d: fallback flush failed: %v", in.Fh, in.NodeId, status)
|
|
}
|
|
}
|
|
if in.ReleaseFlags&fuse.FUSE_RELEASE_FLOCK_UNLOCK != 0 {
|
|
wfs.releaseFlockOwner(in.NodeId, in.LockOwner)
|
|
}
|
|
wfs.ReleaseHandle(FileHandleId(in.Fh))
|
|
}
|