fix(gcssink): prevent empty object finalization on write failure (#8933)

* fix(gcssink): prevent empty object finalization on write failure

The GCS writer was created unconditionally with defer wc.Close(),
which finalizes the upload even when content decryption or copy
fails. This silently overwrites valid objects with empty data.
Remove the unconditional defer, explicitly close on success to
propagate errors, and delete the object on write failure.

* fix(gcssink): use context cancellation instead of obj.Delete on failure

obj.Delete() after a failed write would delete the existing object at
that key, causing data loss on updates. Use a cancelable context
instead — cancelling before Close() aborts the GCS upload without
touching any pre-existing object.
This commit is contained in:
Chris Lu
2026-04-05 16:07:49 -07:00
committed by GitHub
parent 4fd974b16b
commit 72eb93919c

View File

@@ -115,23 +115,32 @@ func (g *GcsSink) CreateEntry(key string, entry *filer_pb.Entry, signatures []in
totalSize := filer.FileSize(entry)
chunkViews := filer.ViewFromChunks(context.Background(), g.filerSource.LookupFileId, entry.GetChunks(), 0, int64(totalSize))
wc := g.client.Bucket(g.bucket).Object(key).NewWriter(context.Background())
defer wc.Close()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
wc := g.client.Bucket(g.bucket).Object(key).NewWriter(ctx)
writeFunc := func(data []byte) error {
_, writeErr := wc.Write(data)
return writeErr
}
var writeErr error
if len(entry.Content) > 0 {
return writeFunc(entry.Content)
writeErr = writeFunc(entry.Content)
} else {
writeErr = repl_util.CopyFromChunkViews(chunkViews, g.filerSource, writeFunc)
}
if err := repl_util.CopyFromChunkViews(chunkViews, g.filerSource, writeFunc); err != nil {
return err
if writeErr != nil {
// Cancel the context to abort the GCS upload without touching
// any existing object at this key.
cancel()
wc.Close()
return writeErr
}
return nil
return wc.Close()
}