filer.remote.sync: skip a holed snapshot even while the filer still holds it

A mid-write snapshot the writer never finished is neither deleted nor
written past: the filer holds it unchanged, so the superseded check never
fires and every retry reads short at the same gap, pinning the sync offset
forever. The upload can never succeed for that entry — skip it like a
superseded one. A finishing write logs its own event and uploads the full
content; a dead write has nothing readable to lose.

The check also covers a chunkless entry that is not a dropped local copy
(e.g. a truncated remote-only entry): its reader yields zero bytes against
a nonzero size, which can silently clobber the remote object with an empty
body where the backend does not verify the length.
This commit is contained in:
user.email committed 2026-10-28 10:44:17 -07:00
1 parent 8cf8abf9f6
commit 25896cf051
2 files changed
+59 -3

No files matched your search

@@ -688,6 +688,23 @@ func TestRetriedWriteFileStopsWhenSuperseded(t *testing.T) {
}
})
t.Run("mid-write snapshot whose hole never fills, no write", func(t *testing.T) {
snapshot := entryWith("IMG_6526.mov", nil, chunkAt("222,01", "e1", 0), chunkAt("220,03", "e3", 2048))
snapshot.Attributes.FileSize = 3072
remote := &failingRemote{err: errors.New("RequestError: send request failed")}
filerClient := &stubFilerClient{entry: snapshot}
_, err := retriedWriteFile(remote, filerClient, dir, snapshot, dest)
if !errors.Is(err, errSuperseded) {
t.Errorf("err = %v, want errSuperseded", err)
}
if remote.writes != 0 {
t.Errorf("wrote %d times, want 0: a snapshot with a hole reads short on every retry", remote.writes)
}
if filerClient.lookups != 0 {
t.Errorf("looked up the filer %d times, want 0: no state makes this uploadable", filerClient.lookups)
}
})
t.Run("still as described, transient failure keeps retrying", func(t *testing.T) {
prev := util.RetryWaitTime
util.RetryWaitTime = 1600 * time.Millisecond // two attempts: 1s, then 1.5s