fix(mount): update parent directory mtime/ctime on deferred file create (#9021)

* fix(mount): update parent directory mtime/ctime on deferred file create

* style: run go fmt on mount package
This commit is contained in:
Chris Lu authored and GitHub committed 2026-04-10 13:05:48 -07:00
1 parent cd82a9cb4b
commit 2264941a17
7 files changed
+39 -13

No files matched your search

-6
View File
@@ -42,12 +42,6 @@ tests/unlink/03.t
tests/rename/23.t
tests/rename/24.t
# ── Parent directory mtime/ctime on deferred file create ───────────────
# When file creation is deferred (not flushed to filer immediately),
# the parent directory mtime/ctime cannot be updated without invalidating
# the just-cached child entry in the metadata cache.
tests/open/00.t
# ── Directory rename permission edge case ──────────────────────────────
# Cross-directory rename of a subdirectory with restricted permissions
# causes cascading test failures within the test file.
+19
View File
@@ -278,6 +278,25 @@ func (mc *MetaCache) UpdateEntry(ctx context.Context, entry *filer.Entry) error
return mc.localStore.UpdateEntry(ctx, entry)
}
// TouchDirMtimeCtime updates the mtime and ctime of a directory entry
// directly in the local metadata cache store. This avoids a filer RPC
// round-trip and the associated metadata event that would invalidate
// recently cached child entries.
func (mc *MetaCache) TouchDirMtimeCtime(ctx context.Context, dirPath util.FullPath, now time.Time) error {
mc.Lock()
defer mc.Unlock()
entry, err := mc.localStore.FindEntry(ctx, dirPath)
if err != nil {
return err
}
if entry == nil {
return nil
}
entry.Attr.Mtime = now
entry.Attr.Ctime = now
return mc.localStore.UpdateEntry(ctx, entry)
}
func (mc *MetaCache) FindEntry(ctx context.Context, fp util.FullPath) (entry *filer.Entry, err error) {
mc.RLock()
defer mc.RUnlock()
+3 -3
View File
@@ -63,9 +63,9 @@ type Option struct {
MountMtime time.Time
MountParentInode uint64
VolumeServerAccess string // how to access volume servers
Cipher bool // whether encrypt data on volume server
UidGidMapper *meta_cache.UidGidMapper
VolumeServerAccess string // how to access volume servers
Cipher bool // whether encrypt data on volume server
UidGidMapper *meta_cache.UidGidMapper
IncludeSystemEntries bool
// Periodic metadata flush interval in seconds (0 to disable)
+12
View File
@@ -1,6 +1,7 @@
package mount
import (
"context"
"os"
"syscall"
"time"
@@ -279,6 +280,17 @@ func (wfs *WFS) touchDirMtimeCtime(dirPath util.FullPath) {
wfs.saveEntry(dirPath, dirEntry)
}
// touchDirMtimeCtimeLocal updates a directory's mtime and ctime directly
// in the local metadata cache, without a filer RPC. This is used for
// deferred file creates where a filer round-trip would invalidate the
// just-cached child entry.
func (wfs *WFS) touchDirMtimeCtimeLocal(dirPath util.FullPath) {
now := time.Now()
if err := wfs.metaCache.TouchDirMtimeCtime(context.Background(), dirPath, now); err != nil {
glog.V(3).Infof("touchDirMtimeCtimeLocal %s: %v", dirPath, err)
}
}
const atimeMapMaxSize = 8192
// setAtime stores an in-memory atime for an inode. The map is bounded;
-1
View File
@@ -56,7 +56,6 @@ func (wfs *WFS) Mkdir(cancel <-chan struct{}, in *fuse.MkdirIn, name string, out
// but BEFORE outputPbEntry writes attributes to the kernel. We restore
// explicitly below instead of using defer so the kernel gets local values.
request := &filer_pb.CreateEntryRequest{
Directory: string(dirFullPath),
Entry: newEntry,
+2
View File
@@ -312,6 +312,8 @@ func (wfs *WFS) createRegularFile(dirFullPath util.FullPath, name string, mode u
if insertErr := wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(string(dirFullPath), newEntry)); insertErr != nil {
glog.Warningf("createFile %s: insert local entry: %v", entryFullPath, insertErr)
}
wfs.inodeToPath.TouchDirectory(dirFullPath)
wfs.touchDirMtimeCtimeLocal(dirFullPath)
glog.V(3).Infof("createFile %s: deferred to flush", entryFullPath)
return inode, newEntry, fuse.OK
}
+3 -3
View File
@@ -46,9 +46,9 @@ type streamMutateMux struct {
cancel context.CancelFunc
grpcConn *grpc.ClientConn // dedicated connection, closed on stream teardown
closed bool
disabled bool // permanently disabled if filer doesn't support the RPC
disabled bool // permanently disabled if filer doesn't support the RPC
stopSend chan struct{} // closed to signal the current sendLoop to exit
generation uint64 // incremented each time a new stream is created
generation uint64 // incremented each time a new stream is created
nextID atomic.Uint64
@@ -64,7 +64,7 @@ type streamMutateMux struct {
type streamMutateReq struct {
req *filer_pb.StreamMutateEntryRequest
errCh chan error // send error feedback
gen uint64 // stream generation this request targets
gen uint64 // stream generation this request targets
}
func newStreamMutateMux(wfs *WFS) *streamMutateMux {