mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-14 07:48:41 +01:00
mount: keep a dirty handle's writes when a peer replaces the file
A peer entry with another inode at the handle's path went down the fallback that destroys the dirty pages and installs the replacement, so close returned OK without the writes. The handle now keeps its writes and base like it does for an unmergeable change, and its flush refuses to update the replacement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2d719fb4ea
commit
0c8ecb3ef6
2 files changed
+42
-3
No files matched your search
@@ -1049,9 +1049,10 @@ func (wfs *WFS) invalidateOpenFileHandle(invalidation meta_cache.EntryInvalidati
|
||||
// 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 contentChanged && fh.dirtyMetadata && sameContentFile(candidate, base) {
|
||||
// An unmergeable change keeps the writes; the flush reports it.
|
||||
if fh.mergePeerContent(candidate, base) {
|
||||
if contentChanged && fh.dirtyMetadata && base != nil {
|
||||
// An unmergeable change or a replaced file keeps the writes; the
|
||||
// flush reports the conflict.
|
||||
if sameContentFile(candidate, base) && fh.mergePeerContent(candidate, base) {
|
||||
fh.adoptPeerBase(proto.Clone(candidate).(*filer_pb.Entry))
|
||||
fh.advanceEntryVersion(candidateTsNs, 0)
|
||||
}
|
||||
|
||||
@@ -2231,3 +2231,41 @@ func TestPeerTruncateWaitsForBufferedWrites(t *testing.T) {
|
||||
t.Fatalf("base %v moved to the peer's truncate", base)
|
||||
}
|
||||
}
|
||||
|
||||
// A peer replacing the file at the handle's path (another inode) leaves the
|
||||
// dirty handle's writes and base alone instead of installing the newcomer.
|
||||
func TestPeerReplacementKeepsDirtyHandle(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}})
|
||||
fh.dirtyMetadata = true
|
||||
pagesBefore := fh.dirtyPages
|
||||
|
||||
replacement := flushConditionEntry(7)
|
||||
replacement.Attributes.Inode = 99
|
||||
resp := &filer_pb.SubscribeMetadataResponse{
|
||||
Directory: "/dir",
|
||||
TsNs: 1500,
|
||||
EventNotification: &filer_pb.EventNotification{
|
||||
OldEntry: flushConditionEntry(1),
|
||||
NewEntry: replacement,
|
||||
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 replacement")
|
||||
}
|
||||
if got := chunkFileKeys(fh.GetEntry().GetEntry()); len(got) != 2 || got[0] != 1 {
|
||||
t.Fatalf("handle chunks %v, want its own", got)
|
||||
}
|
||||
if base := fh.baseEntry.Load(); base.GetAttributes().GetInode() != 42 {
|
||||
t.Fatalf("base %v moved to the replacement", base)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user