Skip to content

feat(local-fs): Support resumable uploads - #635

Closed
lcian wants to merge 18 commits into
mainfrom
lcian/feat/resumable-localfs
Closed

lcian wants to merge 18 commits into
mainfrom
lcian/feat/resumable-localfs

Conversation

@lcian

@lcian lcian commented Sep 16, 2026

Copy link
Copy Markdown
Member

The LocalFS backend now implements resumable uploads.
In-progress uploads are persisted in uploads/<upload id>, where the upload ID is a UUID v7.

This requires adapting the filesystem locking machinery to lock either the object or the session file or both.

We now resolve the TODO comments in the server and Rust client by adding end-to-end test coverage.

Persist resumable sessions on disk, coordinate upload and object publication with filesystem locks, and publish completed uploads atomically.

Add service, server, and Rust client coverage for resuming, cancellation, offset handling, and completion.
@lcian lcian changed the title feat(localfs): Support resumable uploads feat(local-fs): Support resumable uploads Sep 16, 2026
@codecov

This comment was marked as outdated.

Stage each chunk in a temporary file and compare its actual byte count with Content-Length before appending it to the upload session.
@lcian lcian changed the title feat(local-fs): Support resumable uploads feat(localfs): Support resumable uploads Sep 16, 2026
@lcian lcian changed the title feat(localfs): Support resumable uploads feat(local-fs): Support resumable uploads Sep 16, 2026
Store upload sessions directly under the backend root uploads directory because UUID session names cannot collide with object storage paths.
Write chunks directly into the session so interrupted transfers retain their persisted prefix. Reject and roll back bodies that exceed Content-Length.
Cap each resumable write at the advertised content length without failing when the source stream contains more data.
Keep any bytes appended before a resumable chunk write fails. Sync the partial session once, then preserve the distinction between client-stream and backend errors.
Move capped chunk writes and syncing into UploadFile. Simplify put_chunk to return early for incomplete uploads and publish directly once the stored payload reaches its total length.
@lcian

This comment has been minimized.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 608dae8. Configure here.

Comment thread objectstore-service/src/backend/local_fs.rs
Comment thread objectstore-service/src/backend/local_fs.rs
Comment on lines +394 to +396
})
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: A race condition between upload finalization and a progress query can cause the query to incorrectly fail with an UploadSessionGone error, leading the client to restart a successful upload.
Severity: MEDIUM

Suggested Fix

The upload lock should be held continuously throughout the entire finalization process. Avoid dropping the upload_guard and re-acquiring a new lock. Instead, maintain the lock from before the final chunk write until after the file rename is complete to prevent other operations from observing the intermediate state.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: objectstore-service/src/backend/local_fs.rs#L394-L396

Potential issue: A race condition exists during the finalization of a multipart upload.
There is a brief window between when the final chunk is written to disk and when the
upload is officially published by renaming the file. In this window, the upload lock is
temporarily released and re-acquired. If a client sends a progress query
(`upload_offset`) during this exact moment, the query will find the upload file is
complete but not yet published. This causes the `upload_offset` function to incorrectly
return an `UploadSessionGone` error (HTTP 410). The client will interpret this as a
permanent failure and restart the entire upload, even though the original upload was on
the verge of successful completion.

Did we get this right? 👍 / 👎 to inform future reviews.

Use underscore-prefixed LockGuard fields to express that retaining file ownership holds the flock, removing the broad dead-code allowance.
Move session creation, offset access, and publication into UploadFile. Share metadata preamble encoding with normal object drafts while keeping lock orchestration in the backend.
Record resumable publications in the change stream with their full on-disk size and expiry after the atomic rename succeeds.
Serialize each object's resumable sessions with its existing mutation lock and remove the dual-lock publication path.
Construct the byte-limited request reader in put_chunk while preserving partial writes and client-stream error propagation in UploadFile::append.
Match the object lock path and acquisition helpers to main, and mark the behavioral phases of the resumable LocalFS tests.
@lcian lcian closed this Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant