mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-14 07:48:41 +01:00
mount: bring a peer's metadata change into a dirty handle
A metadata-only event moved a dirty handle's base but left its live entry, so the next flush read the peer's chmod as a local edit and published the old mode back. Replay the handle's own changes onto the peer's entry instead, as for a content change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
afa813ff88
commit
659493830b
2 files changed
+60
-16
No files matched your search
+11
-16
@@ -1044,30 +1044,25 @@ func (wfs *WFS) invalidateOpenFileHandle(invalidation meta_cache.EntryInvalidati
|
||||
fh.advanceEntryVersion(candidateTsNs, 0)
|
||||
return
|
||||
}
|
||||
// Dirty pages overlay content, so only a content change invalidates them:
|
||||
// a metadata-only event (chmod, touch, or a committed copy's own event
|
||||
// against an approximate base) leaves them valid. A dirty handle likewise
|
||||
// keeps its diverged entry unless foreign content supersedes it.
|
||||
contentChanged := base == nil || !sameEntryContent(candidate, base)
|
||||
if fh.dirtyMetadata && base != nil && (contentChanged || !sameContentFile(candidate, base)) {
|
||||
// An unmergeable change or a replaced file keeps the writes; the
|
||||
// flush reports the conflict.
|
||||
if contentChanged && sameContentFile(candidate, base) && fh.mergePeerContent(candidate, base) {
|
||||
// A dirty handle replays its own changes onto the peer's entry, keeping
|
||||
// its pages. An unmergeable change or a replaced file keeps the writes;
|
||||
// the flush reports the conflict.
|
||||
if fh.dirtyMetadata && base != nil {
|
||||
if sameContentFile(candidate, base) && fh.mergePeerContent(candidate, base) {
|
||||
fh.adoptPeerBase(proto.Clone(candidate).(*filer_pb.Entry))
|
||||
fh.advanceEntryVersion(candidateTsNs, 0)
|
||||
}
|
||||
return
|
||||
}
|
||||
if contentChanged {
|
||||
// Dirty pages overlay content, so only a content change invalidates them:
|
||||
// a metadata-only event (chmod, touch, or a committed copy's own event
|
||||
// against an approximate base) leaves them valid.
|
||||
if base == nil || !sameEntryContent(candidate, base) {
|
||||
fh.dirtyPages.Destroy()
|
||||
fh.dirtyPages = newPageWriter(fh, wfs.option.ChunkSizeLimit)
|
||||
}
|
||||
if contentChanged || !fh.dirtyMetadata {
|
||||
fh.SetEntry(candidate)
|
||||
fh.baseEntry.Store(proto.Clone(candidate).(*filer_pb.Entry))
|
||||
} else {
|
||||
fh.adoptPeerBase(proto.Clone(candidate).(*filer_pb.Entry))
|
||||
}
|
||||
fh.SetEntry(candidate)
|
||||
fh.baseEntry.Store(proto.Clone(candidate).(*filer_pb.Entry))
|
||||
fh.advanceEntryVersion(candidateTsNs, 0)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2276,3 +2276,52 @@ func TestPeerReplacementKeepsDirtyHandle(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A peer's metadata-only change reaches a dirty handle's live entry, so its
|
||||
// next flush does not publish the stale value back; the handle's own edits
|
||||
// stay on top.
|
||||
func TestPeerChmodReachesDirtyHandle(t *testing.T) {
|
||||
for _, name := range []string{"peer-only", "both-changed"} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
wfs := newInvalidateTestWFS(t)
|
||||
|
||||
inode := wfs.inodeToPath.Lookup(util.FullPath("/dir/file"), time.Now().Unix(), false, false, 0, false)
|
||||
fh, _, _, _ := wfs.fhMap.AcquireFileHandle(wfs, inode, flushConditionEntry(1), 0, 0)
|
||||
fh.AddChunks([]*filer_pb.FileChunk{{FileId: "1,0300000001", Offset: 2, Size: 1, ModifiedTsNs: 3}})
|
||||
want := uint32(0600)
|
||||
if name == "both-changed" {
|
||||
fh.UpdateEntry(func(e *filer_pb.Entry) { e.Attributes.FileMode = 0640 })
|
||||
want = 0640
|
||||
}
|
||||
fh.dirtyMetadata = true
|
||||
pagesBefore := fh.dirtyPages
|
||||
|
||||
peer := flushConditionEntry(1)
|
||||
peer.Attributes.FileMode = 0600
|
||||
resp := &filer_pb.SubscribeMetadataResponse{
|
||||
Directory: "/dir",
|
||||
TsNs: 1500,
|
||||
EventNotification: &filer_pb.EventNotification{
|
||||
OldEntry: flushConditionEntry(1),
|
||||
NewEntry: peer,
|
||||
NewParentPath: "/dir",
|
||||
},
|
||||
}
|
||||
if err := wfs.metaCache.ApplyMetadataResponse(context.Background(), resp, meta_cache.SubscriberMetadataResponseApplyOptions); err != nil {
|
||||
t.Fatalf("apply: %v", err)
|
||||
}
|
||||
wfs.metaCache.WaitForEntryInvalidations()
|
||||
|
||||
if fh.dirtyPages != pagesBefore {
|
||||
t.Fatal("dirty pages destroyed by a peer chmod")
|
||||
}
|
||||
entry := fh.GetEntry().GetEntry()
|
||||
if got := entry.Attributes.FileMode; got != want || len(entry.Chunks) != 2 {
|
||||
t.Fatalf("handle mode %o with %d chunks, want %o with its own chunk", got, len(entry.Chunks), want)
|
||||
}
|
||||
if base := fh.baseEntry.Load(); base.GetAttributes().GetFileMode() != 0600 {
|
||||
t.Fatalf("base %v, want the peer's entry", base)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user