Skip to content

fix(s3): derive the delta upload's mtime instead of asking for it - #756

Merged
axpnet merged 1 commit into
mainfrom
fix/delta-mtime-and-declared-limits
Sep 8, 2026
Merged

fix(s3): derive the delta upload's mtime instead of asking for it#756
axpnet merged 1 commit into
mainfrom
fix/delta-mtime-and-declared-limits

Conversation

@axpnet

@axpnet axpnet commented Sep 8, 2026

Copy link
Copy Markdown
Member

The defect

upload_delta_multipart took source_mtime as a parameter. upload_multipart_streaming, one screen away in the same file, derives it from the local path with Self::source_mtime_metadata(local_path).

The executor already holds local_path, since that is where it reads the PUT parts from, so the parameter existed only as a way to forget. A caller passing None leaves the rebuilt object without the x-amz-meta-mtime the original carried, silently. From then on every sync that compares timestamps never finds that object equal and re-uploads the whole file, which is the exact outcome a delta upload exists to avoid.

The caller that would have had to remember does not exist yet. The adapter arm is the next tranche, which is when this would have been found: on a real transfer, by a user whose sync stopped converging.

It came from the G Lane, while they were checking whether their own S3 work collided with #755. Nothing in that pull request's sixteen green checks could have shown it. The code compiles with the parameter, create_multipart_upload accepts None by design, and no test asked what reached the wire.

The test asks what reached the wire

a_delta_upload_carries_the_source_mtime_like_an_ordinary_one asserts that x-amz-meta-mtime arrived on the CreateMultipartUpload, carrying the source file's own value. Not that a helper can compute one, which is the version that would have passed either way.

The mock backend now records the header on each create, so the absence is observable rather than inferred. Seen failing on a version where the executor passes None, which is precisely the shape the parameter allowed:

a_delta_upload_carries_the_source_mtime_like_an_ordinary_one ... FAILED
the create must carry x-amz-meta-mtime

Two limits, written down instead of left silent

Both come from the appendix's constraint sheet, and neither is enforced in code. That is now stated in the executor with its reasoning, so the next reader does not have to decide whether it is an oversight.

A ranged copy needs a source object above 5 MB. There is no check because there is no way to reach the case: a baseline under 5 MB cannot produce a match as wide as the grid, which is at least 8 MiB, so the planner demotes every copy run and refuses the plan before a single UploadPartCopy is built. The argument is the reason for the absence, not an excuse for it.

"Verify the result, do not assume it" is a guardrail to restate, not an omission to fill. The appendix asks for the multipart to be created with a checksum algorithm so S3 returns a full-object digest comparable against the local file. For a multipart the object-level checksum is COMPOSITE: a hash of the part hashes with an -N suffix, which is the same shape the appendix itself already documents for the multipart ETag in 04(b), and it is not comparable to a digest computed over the local file. A whole-object digest needs the full-object checksum type, which S3 offers for the CRC families and not for SHA256, with uneven support across S3-compatible backends.

Measured alongside this, by the test station's rclone comparison bench on 300 MiB server-side copies:

copy-300m-minio  aeroftp integrity  CHECK=ok :: size match (no remote sha)
copy-300m-r2     aeroftp integrity  CHECK=ok :: size match (no remote sha)

with their own caveat attached: the bench asks for sha256 and rclone's S3 backend normally exposes the ETag, so that is partly a limit of the reading side. It is not proof that x-amz-checksum-algorithm fails; it is evidence that a multipart object is not hash-verifiable by a third-party client today on either backend, which is why deferring closes nothing that currently works.

On the yardstick, since the numbers will be quoted

The delta's saving should be cited against our own rsync path, not against rclone. rclone has no delta on S3 and resends the whole object, so "16 MiB against 1 GiB" measures that the other tool does not play, not how well this one does. The informative comparison is the wire ratio our native rsync achieves on the same file and the same edit, because that is what prices the aligned grid: near parity on an append, roughly 60 percent more wire on a middle edit that straddles two cells, and everything against nothing on an insertion at offset zero, which is Tier 1's declared blind spot. That gap is the field data the deferred Tier 2 decision rests on.

Gate

cargo fmt --all --check, cargo clippy --all-targets -- -D warnings, and cargo test --lib s3 with 180 tests passing and 3 ignored (the env-gated live lane), run on the branch merged over main at bf15c887c.

Summary by CodeRabbit

  • Bug Fixes
    • Improved reliability of delta multipart uploads by preserving source modification-time metadata.
    • Added safeguards for conditional-copy failures and truncated-file cleanup.
    • Improved consistency across live upload and download round trips.

`upload_delta_multipart` took `source_mtime` as a parameter while `upload_multipart_streaming`, one screen away, derives it from the local path. The executor already holds `local_path`, so the parameter existed only as a way to forget: a caller passing `None` leaves the rebuilt object without the mtime the original carried, silently, and from then on every sync that compares timestamps never finds it equal and re-uploads the whole file. That is the opposite of what a delta upload is for.

The caller that would have had to remember does not exist yet. The adapter arm is the next tranche, which is exactly when this would have been found, on a real transfer, by a user whose sync stopped converging.

Raised by the G Lane while checking whether their S3 work collided with this one. Nothing in sixteen green checks could have shown it: the code compiles either way, and no test asked what reached the wire.

So the test asks. `a_delta_upload_carries_the_source_mtime_like_an_ordinary_one` asserts that `x-amz-meta-mtime` arrived on the CreateMultipartUpload with the source file's own value, not that a helper can compute one. Seen failing on a version that passes `None`, which is the shape the parameter allowed.

Two limits from the appendix are now written down in the executor rather than left as silences. The rule that a ranged copy needs a source above 5 MB has no check because it cannot be reached: a baseline under 5 MB cannot produce a match as wide as the grid, so the planner refuses before an UploadPartCopy exists, and the argument is the reason for the absence rather than an excuse for it. And the appendix's request to verify the result with a full-object digest is not an omission to fill but a guardrail to restate: a multipart's object-level checksum is COMPOSITE, a hash of the part hashes with an `-N` suffix, the same shape the appendix already documents for the multipart ETag, and it is not comparable to a digest over the local file. A whole-object digest needs the full-object checksum type, which S3 offers for the CRC families and not for SHA256, unevenly across compatible backends. Measured alongside: on MinIO and R2 a multipart object is not hash-verifiable by a third-party client today.

Signed-off-by: axpnet <45786925+axpnet@users.noreply.github.com>
@snyk-io

snyk-io Bot commented Sep 8, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: e8eb62fd-0a72-428d-b584-94fafbfec91e

📥 Commits

Reviewing files that changed from the base of the PR and between bf15c88 and 43a9fc0.

📒 Files selected for processing (1)
  • src-tauri/src/providers/s3.rs

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

Delta multipart uploads now derive source mtime metadata from the local file. Mock S3 state captures the metadata, and tests cover metadata preservation, refusal behavior, failure cleanup, and live round trips.

Changes

Delta multipart metadata

Layer / File(s) Summary
Local mtime derivation
src-tauri/src/providers/s3.rs
upload_delta_multipart derives mtime metadata from local_path. Live callers use the updated signature.
Metadata and regression validation
src-tauri/src/providers/s3.rs
The mock server records mtime metadata. Tests verify metadata transmission, refusal behavior, conditional-copy failures, truncated-file cleanup, progress callbacks, and successful uploads.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 43a9f

Delta multipart uploads now preserve the local file modification time in S3 metadata, preventing unnecessary later timestamp-based reuploads. The updated metadata and failure-path coverage shows no current merge-readiness risk.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: deriving the delta upload modification time from the local path instead of requiring it as an argument.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 8 functions across 1 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/delta-mtime-and-declared-limits

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@axpnet
axpnet merged commit 12747c5 into main Sep 8, 2026
19 checks passed
@axpnet
axpnet deleted the fix/delta-mtime-and-declared-limits branch September 8, 2026 07:37
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