fix(storage): abort backend multipart when reaping abandoned sys_upload_session (#2970)#2999
Merged
Merged
Conversation
…ad_session (#2970) The sys_upload_session lifecycle (#2984) reaps abandoned/terminal chunked-upload session rows but not the underlying backend multipart — on S3 an initiated-but-not-completed multipart keeps its parts billable and invisible until AbortMultipartUpload, so reaping only the row stranded them (backend_upload_id, the sole pointer, gone). createUploadSessionReapGuard registers a LifecycleReapGuard on sys_upload_session that aborts the backend multipart before the row is deleted: skips 'completed' sessions (multipart already an object — an abort would NoSuchUpload-error), re-seeds the S3 uploadId->key map from the row (cold sweeps lack the live in-process map), and vetoes (keeps the row for retry) on abort failure so the pointer survives. Local adapter parts dir removed the same way. Verified: 7 new unit tests (S3 key re-seed, completed-skip, no-backend, veto-on-failure, local adapter, no-abort adapter); service-storage 99; dogfood matrix drives a real chunked upload → part on disk → sweep → row gone AND parts dir aborted. Closes the last #2970 sub-follow-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0187NT3Qer9oep5dCRb9b8Lt
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
7 tasks
os-zhuang
added a commit
that referenced
this pull request
Jul 16, 2026
…ion guard note (#3009) Documents the attachment access model landed across #2984/#2999/#2538 (#2970): parent-derived read/create/delete, inherited list visibility, authenticated parent-scoped downloads, the enable.files opt-in gate, the member_default no-delete-baseline caveat, the storage-byte lifecycle (sys_file + sys_upload_session reap guards), and an enforcement-summary + error-code table. Registered in permissions/meta.json. Also names sys_upload_session as the second reap-guard consumer in the ADR-0057 amendment. Claude-Session: https://claude.ai/code/session_0187NT3Qer9oep5dCRb9b8Lt Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the last sub-follow-up on #2970 (follow-up to merged #2984).
The leak
The
sys_upload_sessionlifecycle (added in #2984) reaps abandoned/terminal chunked-upload session rows, but not the underlying backend multipart upload. On S3 an initiated-but-not-completed multipart keeps its already-uploaded parts billable and invisible to normal listing until an explicitAbortMultipartUpload— so reaping only the row stranded them, withbackend_upload_id(the sole pointer) gone.The fix
createUploadSessionReapGuardregisters aLifecycleReapGuardonsys_upload_session(same seam as thesys_fileguard) that aborts the backend multipart before the row is deleted:completedsessions → skip abort (their multipart already became a real object; an abort wouldNoSuchUpload-error and wedge the reap) — just reap the row.backend_upload_id/ adapter can't abort → nothing to abort, reap the row.uploadId → keymap from the row (a cold sweep lacks the live in-process map thatsetUploadKeypopulates during upload; reached viagetInner()since it's S3-specific and not forwarded by the swappable proxy), thenabortChunkedUpload. Confirm on success; veto (keep the row for retry) on failure so the pointer survives. The local adapter's parts dir is removed the same way.The abort API already existed (
IStorageService.abortChunkedUpload, S3AbortMultipartUploadCommand, local parts-dirrm); this wires it into the sweep.Verification
@objectstack/service-storage99 ✓): 7 new guard tests — S3 key re-seed + abort + reap, completed-skip, no-backend_upload_id, veto-on-abort-failure, local adapter (nogetInner/setUploadKey), no-abort adapter; plugin-wiring test asserts bothsys_fileandsys_upload_sessionguards register.<rootDir>/.parts/<uploadId>/) → backdateexpires_at→lifecycle.sweep()→ asserts the session row is gone and the parts dir was aborted (removed). Matrix 15 ✓; the full dogfood suite runs as this PR's Dogfood Regression Gate.check:api-surface✓ (internal export only).With this, all of #2970 — the six items plus this sub-follow-up — is complete.
🤖 Generated with Claude Code
Generated by Claude Code