From f535fca8f6150eaa662b30534350b8d158a11584 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 6 Nov 2026 01:24:38 -0800 Subject: [PATCH] filer.remote.sync: take the start time before the mount lookup A bucket created between a not-found lookup and the end of the offset read had its creation before a start taken afterwards, so the gateway never created its remote bucket. Co-Authored-By: Claude Opus 5.5 --- weed/command/filer_remote_sync_dir.go | 7 ++++--- weed/command/filer_remote_sync_dir_test.go | 14 ++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/weed/command/filer_remote_sync_dir.go b/weed/command/filer_remote_sync_dir.go index 53c9b01161..49f21df0a6 100644 --- a/weed/command/filer_remote_sync_dir.go +++ b/weed/command/filer_remote_sync_dir.go @@ -1108,6 +1108,7 @@ func collectLastSyncOffset(filerClient filer_pb.FilerClient, grpcDialOption grpc // retry loop instead of skipping every event since the failure. Only a // directory that does not exist yet, with no saved offset, starts now. func resolveLastSyncOffset(mountedDir string, timeAgo time.Duration, lookupMount func() (*filer_pb.Entry, error), readOffset func() (int64, error)) (time.Time, error) { + startedAt := time.Now() var lastOffsetTs time.Time if timeAgo == 0 { mountedDirEntry, err := lookupMount() @@ -1118,7 +1119,7 @@ func resolveLastSyncOffset(mountedDir string, timeAgo time.Duration, lookupMount return time.Unix(0, lastOffsetTsNs), nil } if offsetErr == nil && errors.Is(err, filer_pb.ErrNotFound) { - return time.Now(), nil + return startedAt, nil } return time.Time{}, fmt.Errorf("get mounted directory %s: %w; no saved sync offset (%v)", mountedDir, err, offsetErr) } @@ -1135,10 +1136,10 @@ func resolveLastSyncOffset(mountedDir string, timeAgo time.Duration, lookupMount lastOffsetTs = time.Unix(mountedDirEntry.Attributes.Crtime, 0) } } else { - lastOffsetTs = time.Now() + lastOffsetTs = startedAt } } else { - lastOffsetTs = time.Now().Add(-timeAgo) + lastOffsetTs = startedAt.Add(-timeAgo) lastOffsetTsNs, err := readOffset() if err != nil { // an unread offset must not resolve to now minus timeAgo: the diff --git a/weed/command/filer_remote_sync_dir_test.go b/weed/command/filer_remote_sync_dir_test.go index 54ff161958..1ced1240cd 100644 --- a/weed/command/filer_remote_sync_dir_test.go +++ b/weed/command/filer_remote_sync_dir_test.go @@ -670,11 +670,17 @@ func TestResolveLastSyncOffsetReturnsTheErrorToTheRetryLoop(t *testing.T) { t.Fatalf("got %v, want the lookup error: without a saved offset the caller's retry loop decides, not an exit", err) } - // no directory and no saved offset yet: a first run starts now + // no directory and no saved offset yet: a first run starts no later than + // the lookup, so a bucket created while the offset is read is replayed started := time.Now() - notFound := func() (*filer_pb.Entry, error) { return nil, fmt.Errorf("lookup /buckets: %w", filer_pb.ErrNotFound) } - if got, err := resolveLastSyncOffset("/buckets", 0, notFound, noOffset); err != nil || got.Before(started) { - t.Fatalf("got %v, %v; want now and no error before the directory exists", got, err) + var lookedUp time.Time + notFound := func() (*filer_pb.Entry, error) { + lookedUp = time.Now() + return nil, fmt.Errorf("lookup /buckets: %w", filer_pb.ErrNotFound) + } + slowNoOffset := func() (int64, error) { time.Sleep(10 * time.Millisecond); return 0, nil } + if got, err := resolveLastSyncOffset("/buckets", 0, notFound, slowNoOffset); err != nil || got.Before(started) || got.After(lookedUp) { + t.Fatalf("got %v, %v; want a start between %v and the lookup at %v", got, err, started, lookedUp) } // the mount answers but the saved offset is unreadable: never its creation time