From 1c003d0e524c8c779324d737b94c2fd5048fe14d Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 11 Oct 2026 20:34:56 -0700 Subject: [PATCH] filer: stamp the rename event with the deferred copy's result A remote-only rename over a remote-only target queued the new entry's event with the source's stamp before the deferred copy ran. Peers replaying it saved a stamp the copy had already superseded, and the sync serving such a peer rejected the destination as a match and tried to copy the deleted source. The event now carries the copy's result and drops the pending-source marker once it lands. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- weed/server/filer_grpc_server_rename.go | 7 +++++-- weed/server/filer_grpc_server_rename_remote_test.go | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/weed/server/filer_grpc_server_rename.go b/weed/server/filer_grpc_server_rename.go index dd556c7e8..63e8cdd40 100644 --- a/weed/server/filer_grpc_server_rename.go +++ b/weed/server/filer_grpc_server_rename.go @@ -208,8 +208,9 @@ func (fs *FilerServer) finishRemoteCopies(ctx context.Context, pendings []*pendi // applyRemoteCopies brings the queued rename events in line with the deferred // copies. The stamp is written with a plain UpdateEntry, which publishes no -// event, so the rename event carries it to peer filers and subscribers. A -// failed copy keeps the old object, which the entry still describes. +// event, so the rename event carries it, without the pending-source marker, +// to peer filers and subscribers. A failed copy keeps the old object, which +// the entry still reads through the marker. func applyRemoteCopies(events []metadataEvent, pendings []*pendingRemoteCopy) { for i := range events { event := &events[i] @@ -222,6 +223,8 @@ func applyRemoteCopies(events []metadataEvent, pendings []*pendingRemoteCopy) { } else if pending.copied != nil && event.newEntry != nil { newEntry := event.newEntry.ShallowClone() newEntry.Remote = pending.copied + newEntry.Extended = maps.Clone(newEntry.Extended) + delete(newEntry.Extended, filer.ExtRemoteRenameSourceKey) event.newEntry = newEntry } } diff --git a/weed/server/filer_grpc_server_rename_remote_test.go b/weed/server/filer_grpc_server_rename_remote_test.go index f53ccfd13..8750b3a47 100644 --- a/weed/server/filer_grpc_server_rename_remote_test.go +++ b/weed/server/filer_grpc_server_rename_remote_test.go @@ -154,6 +154,11 @@ func TestRenameRemoteOnlyEntryOverTargetKeepsTheCopy(t *testing.T) { assert.Equal(t, uint64(101), dst.Attr.Inode) assert.Equal(t, `"copy"`, dst.Remote.RemoteETag) assert.Empty(t, dst.Extended[filer.ExtRemoteRenameSourceKey]) + // peers replay the post-copy state, not the source's stamp the entry + // committed with + newEvent := events[1].notification.NewEntry + assert.Equal(t, `"copy"`, newEvent.RemoteEntry.RemoteETag) + assert.Empty(t, newEvent.Extended[filer.ExtRemoteRenameSourceKey]) } func TestRenameRemoteOnlyEntryCopyFailureKeepsTheOldObject(t *testing.T) {