mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 03:45:06 +03:00
fix(storage): don't let a purge round delete a file that went live
The drain loop fed each listing straight back into the next delete. Deleting a version id is unconditional, so a restore or a new upload to the same path while the purge was running would be picked up by the following round and destroyed. Deleting the delete marker promotes nothing in Storage, so mid-drain every surviving row is still archived. A current version appearing in a later round therefore means the path was written again, and the purge now stops instead of claiming it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N3euXsz25sTybsGTcxCFfn
This commit is contained in:
2 files changed
+15
No files matched your search
@@ -79,6 +79,14 @@ describe('useArchivedObjectPurgeMutation', () => {
|
||||
expect(deleted).toEqual([])
|
||||
})
|
||||
|
||||
it('stops rather than delete a version that went live mid-purge', async () => {
|
||||
// The first round clears the history; by the second, the path has been written again.
|
||||
mockListPages([[marker(), row('v2')], [row('v-new', { archived_at: null })]])
|
||||
|
||||
await expect(purge()).rejects.toThrow(/restored or replaced/)
|
||||
expect(deleted).toEqual([[MARKER, 'v2']])
|
||||
})
|
||||
|
||||
it('gives up rather than spin when a round deletes nothing', async () => {
|
||||
mockListPages([[marker(), row('v1')]])
|
||||
|
||||
|
||||
@@ -69,6 +69,13 @@ export const useArchivedObjectPurgeMutation = ({
|
||||
await deleteVersions(versions)
|
||||
|
||||
const remaining = await listVersions()
|
||||
// Deleting the marker promotes nothing, so nothing here should be live. One that is
|
||||
// arrived while the purge was running — a restore, or a new upload to the same path —
|
||||
// and belongs to the user, not to the archive the next round would hand to `del`.
|
||||
const live = remaining.find((version) => version.isCurrent)
|
||||
if (live !== undefined && live.versionId !== archivedObjectId) {
|
||||
throw new Error(`${path} was restored or replaced while it was being deleted`)
|
||||
}
|
||||
// Nothing went away, so another round would spin rather than make progress.
|
||||
if (remaining.length >= versions.length) {
|
||||
throw new Error(`Some versions of ${path} could not be deleted`)
|
||||
|
||||
Reference in new issue
Block a user