From 84b29430f1663fce117726e57d6942371be88ff5 Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Fri, 18 Sep 2026 20:30:05 +0200 Subject: [PATCH] fix(upload): fail chunk when upload session record vanished (#2938) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an upload session's KV record disappears mid-upload (expired TTL, eviction, or a racing cancel/fail), MarkChunkUploaded returns (false, nil) — the chunk request then succeeded without ever completing the upload, leaving a placeholder permanently stuck at size 0 in "uploading" state. processChunkUpload now re-checks the session record on a non-complete mark and returns CodeUploadSessionExpired when it is gone, so clients retry loudly instead of stalling. The same guard applies to the new ranged-PUT path (409 + retry). Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- service/explorer/upload.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/service/explorer/upload.go b/service/explorer/upload.go index 4332a110..7643732e 100644 --- a/service/explorer/upload.go +++ b/service/explorer/upload.go @@ -205,6 +205,15 @@ func processChunkUpload(c *gin.Context, m manager.FileManager, session *fs.Uploa if err != nil { return err } + if !allReceived { + // A missing session record (expired TTL, eviction, or a racing + // cancel) means completion can never trigger — fail loudly so the + // client retries instead of leaving a permanently stuck placeholder. + dep := dependency.FromContext(c) + if _, ok := dep.KV().Get(manager.UploadSessionCachePrefix + session.Props.UploadSessionID); !ok { + return serializer.NewError(serializer.CodeUploadSessionExpired, "", nil) + } + } if allReceived { _, err := m.CompleteUpload(ctx, session) if err != nil {