filer: bootstrap a timestamp-less peer entry onto a missing local path (#11975)

A zero local commit time conflated 'no local state' with 'state at the
epoch', so a missing entry was skipped whenever the sender reported no
commit time and the entry mtime was at or before the epoch. Only hold
the copy off when a local entry actually exists.
This commit is contained in:
Chris Lu authored and GitHub committed 2026-10-23 18:45:09 -07:00
1 parent 85f2f9d2d4
commit 0d4e11a6e5
2 files changed
+53 -1

No files matched your search

+16 -1
View File
@@ -788,7 +788,22 @@ func (f *Filer) applyTraversedEntry(ctx context.Context, entry *Entry, peerTsNs
return fmt.Errorf("check local state for %s: %w", entry.FullPath, err)
}
if localTsNs >= peerTsNs {
return nil
if localTsNs != 0 {
return nil
}
// Zero timestamps carry no order: keep the path only when an entry
// exists locally — a missing one takes the peer's copy, or an
// epoch-dated entry from a peer reporting no commit time is dropped.
if f.Store == nil {
return nil
}
local, findErr := f.Store.FindEntry(ctx, entry.FullPath)
if findErr != nil && !errors.Is(findErr, filer_pb.ErrNotFound) {
return fmt.Errorf("check local state for %s: %w", entry.FullPath, findErr)
}
if local != nil {
return nil
}
}
if insertErr := f.Store.InsertEntry(ctx, entry); insertErr != nil {
// Entry already exists (root dir, or partial previous bootstrap);
+37
View File
@@ -575,6 +575,43 @@ func TestBootstrapUpdatesExistingByCommitTime(t *testing.T) {
}
}
// A missing local entry has no commit time to order against: the peer copy
// must apply even when both sides are timestamp-less — an epoch-dated entry
// from a peer that predates EntryTsNs is still bootstrapped, while an
// existing zero-mtime local entry keeps its place.
func TestBootstrapCopiesMissingEntryWithoutCommitTime(t *testing.T) {
store := newStubFilerStore()
f := newReplayTestFiler(store)
missing := util.NewFullPath("/dir", "missing")
existing := util.NewFullPath("/dir", "existing")
store.entries[string(existing)] = &Entry{
FullPath: existing,
Attr: Attr{Mtime: time.Unix(0, 0)},
Content: []byte("zero-mtime local entry"),
}
for _, path := range []util.FullPath{missing, existing} {
entry := &Entry{
FullPath: path,
Attr: Attr{Mtime: time.Unix(0, 0)},
Content: []byte("epoch-dated peer entry"),
}
if err := f.applyTraversedEntry(context.Background(), entry, 0); err != nil {
t.Fatalf("applyTraversedEntry %s: %v", path, err)
}
}
got, err := store.FindEntry(context.Background(), missing)
if err != nil {
t.Fatalf("missing local entry must take the peer copy: %v", err)
}
if string(got.Content) != "epoch-dated peer entry" {
t.Fatalf("content = %q, want peer entry applied", got.Content)
}
if got, _ := store.FindEntry(context.Background(), existing); string(got.Content) != "zero-mtime local entry" {
t.Fatalf("content = %q, existing local entry must be kept", got.Content)
}
}
// A delete whose commit record failed to persist still blocks a stale
// replayed create: the pending record stands in until the retry lands it.
func TestPendingDeleteRecordBlocksStaleReplay(t *testing.T) {