diff --git a/weed/mount/filehandle.go b/weed/mount/filehandle.go index 77468adbc0..6293797254 100644 --- a/weed/mount/filehandle.go +++ b/weed/mount/filehandle.go @@ -250,6 +250,15 @@ func (fh *FileHandle) noteTruncate(size uint64) { fh.truncatedTo.Store(&truncateMark{base: base, size: int64(size)}) } +// adoptPeerBase moves a dirty handle's base to a peer's entry. A truncate the +// handle has not published yet still applies over the new base. +func (fh *FileHandle) adoptPeerBase(base *filer_pb.Entry) { + old := fh.baseEntry.Swap(base) + if m := fh.truncatedTo.Load(); m != nil && m.base == old { + fh.truncatedTo.Store(&truncateMark{base: base, size: m.size}) + } +} + // truncatedSince is the lowest size truncated to over base, or -1. func (fh *FileHandle) truncatedSince(base *filer_pb.Entry) int64 { if m := fh.truncatedTo.Load(); m != nil && m.base == base { diff --git a/weed/mount/weedfs.go b/weed/mount/weedfs.go index 3a6633c96f..dd7f7271c9 100644 --- a/weed/mount/weedfs.go +++ b/weed/mount/weedfs.go @@ -1052,7 +1052,7 @@ func (wfs *WFS) invalidateOpenFileHandle(invalidation meta_cache.EntryInvalidati if contentChanged && fh.dirtyMetadata && sameContentFile(candidate, base) { // An unmergeable change keeps the writes; the flush reports it. if fh.mergePeerContent(candidate, base) { - fh.baseEntry.Store(proto.Clone(candidate).(*filer_pb.Entry)) + fh.adoptPeerBase(proto.Clone(candidate).(*filer_pb.Entry)) fh.advanceEntryVersion(candidateTsNs, 0) } return @@ -1063,8 +1063,10 @@ func (wfs *WFS) invalidateOpenFileHandle(invalidation meta_cache.EntryInvalidati } 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.baseEntry.Store(proto.Clone(candidate).(*filer_pb.Entry)) fh.advanceEntryVersion(candidateTsNs, 0) return } diff --git a/weed/mount/weedfs_invalidate_open_handle_test.go b/weed/mount/weedfs_invalidate_open_handle_test.go index a13aff42b4..7aa6c154b3 100644 --- a/weed/mount/weedfs_invalidate_open_handle_test.go +++ b/weed/mount/weedfs_invalidate_open_handle_test.go @@ -2159,3 +2159,38 @@ func TestUnmergeablePeerChangeKeepsDirtyHandle(t *testing.T) { t.Fatalf("base %v moved to the peer's entry", base) } } + +// A peer event that moves a dirty handle's base keeps its unpublished +// truncate, so a later merge still clips peer chunks at it. +func TestPeerEventKeepsPendingTruncate(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, 2), 0, 0) + fh.dirtyMetadata = true + fh.noteTruncate(1) + + chmod := flushConditionEntry(1, 2) + chmod.Attributes.FileMode = 0600 + resp := &filer_pb.SubscribeMetadataResponse{ + Directory: "/dir", + TsNs: 1500, + EventNotification: &filer_pb.EventNotification{ + OldEntry: flushConditionEntry(1, 2), + NewEntry: chmod, + NewParentPath: "/dir", + }, + } + if err := wfs.metaCache.ApplyMetadataResponse(context.Background(), resp, meta_cache.SubscriberMetadataResponseApplyOptions); err != nil { + t.Fatalf("apply: %v", err) + } + wfs.metaCache.WaitForEntryInvalidations() + + base := fh.baseEntry.Load() + if base.GetAttributes().GetFileMode() != 0600 { + t.Fatalf("base %v did not move to the peer's entry", base) + } + if got := fh.truncatedSince(base); got != 1 { + t.Fatalf("pending truncate %d after the base moved, want 1", got) + } +}