filer.remote.sync: stamp a copy of the event entry

updateLocalEntry set the RemoteEntry and dropped the rename marker on the
caller's entry before the RPC. A transient stamp failure retries the same
event in place, which then read as already synced and skipped both the
upload and the stamp. The stub filer now applies a successful update the
way the filer does, since tests had relied on that shared object.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chris LuandClaude Opus 5.5 committed 2026-11-06 01:27:19 -08:00
1 parent 0a1b6c2944
commit 1f332e5866
2 files changed
+29 -1

No files matched your search

+1
View File
@@ -1243,6 +1243,7 @@ func updateLocalEntry(filerClient filer_pb.FilerClient, dir string, entry *filer
remoteEntry = proto.Clone(remoteEntry).(*filer_pb.RemoteEntry)
remoteEntry.LastLocalSyncTsNs = time.Now().UnixNano()
expected := proto.Clone(entry).(*filer_pb.Entry)
entry = proto.Clone(entry).(*filer_pb.Entry)
entry.RemoteEntry = remoteEntry
delete(entry.Extended, filer.ExtRemoteRenameSourceKey)
err := filerClient.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
+28 -1
View File
@@ -456,6 +456,11 @@ func (c *stubFilerClient) UpdateEntry(_ context.Context, req *filer_pb.UpdateEnt
if c.updateErr != nil {
return &filer_pb.UpdateEntryResponse{}, c.updateErr
}
if key := req.Directory + "/" + req.Entry.GetName(); c.entries[key] != nil {
c.entries[key] = req.Entry
} else if c.entry != nil && c.entry.Name == req.Entry.GetName() {
c.entry = req.Entry
}
return &filer_pb.UpdateEntryResponse{}, nil
}
@@ -1549,6 +1554,28 @@ func TestDeleteEventAbsentRemoteObjectIsSuccess(t *testing.T) {
})
}
// TestUpdateLocalEntryLeavesTheEventEntryAlone: a transient stamp failure
// retries the same event in place, so the event must still read as unsynced
// and keep its rename source, or the retry skips the upload and the stamp.
func TestUpdateLocalEntryLeavesTheEventEntryAlone(t *testing.T) {
const dir = "/buckets/b/vol"
event := entryWith("state.db", nil, chunk("3,01", "e1"))
event.Extended = map[string][]byte{filer.ExtRemoteRenameSourceKey: []byte("src")}
filerClient := &stubFilerClient{updateErr: status.Error(codes.Unavailable, "filer is shutting down")}
if err := updateLocalEntry(filerClient, dir, event, &filer_pb.RemoteEntry{StorageName: "gcs", RemoteSize: 7}); err == nil {
t.Fatal("err = nil, want the transient failure")
}
if event.RemoteEntry != nil || len(event.Extended[filer.ExtRemoteRenameSourceKey]) == 0 {
t.Errorf("event entry = %v, want it untouched by the failed stamp", event)
}
if !shouldSendToRemote(event) {
t.Error("shouldSendToRemote = false after a failed stamp: the in-place retry would skip the upload")
}
if sent := filerClient.updated.GetEntry(); sent.GetRemoteEntry() == nil || len(sent.GetExtended()[filer.ExtRemoteRenameSourceKey]) > 0 {
t.Errorf("stamp request entry = %v, want the remote entry set and the rename source dropped", sent)
}
}
// TestUpdateLocalEntrySkipsDeletedEntry: the stamp that follows an upload lands
// on an entry a later event already deleted. The filer answers "not found"; the
// delete's own event follows in the log, so the stamp is skipped rather than
@@ -1834,7 +1861,7 @@ func TestCompleteRemoteOnlyRenameClearsThePendingSourceFirst(t *testing.T) {
t.Run("a marker still in place keeps the source", func(t *testing.T) {
// the stamp did not land: the filer still has the marker
filerClient := &stubFilerClient{entry: pending()}
filerClient := &stubFilerClient{entry: pending(), updateErr: status.Error(codes.FailedPrecondition, "entry changed")}
remote := &recordingRemote{objects: objects(), statETag: `"copy"`}
if err := completeRemoteOnlyRename(filerClient, remote, "/buckets/b/dst", pending(), renameSource{path: "/buckets/b/src/a.jpg", loc: oldDest}, dest, ""); err == nil {
t.Fatal("err = nil, want the event retried")