Skip to content

fix(transfer): stop paying one server round trip per file for the multi-stream decision - #757

Merged
axpnet merged 1 commit into
mainfrom
fix/multi-thread-probe-size-hint
Sep 8, 2026
Merged

fix(transfer): stop paying one server round trip per file for the multi-stream decision#757
axpnet merged 1 commit into
mainfrom
fix/multi-thread-probe-size-hint

Conversation

@axpnet

@axpnet axpnet commented Sep 8, 2026

Copy link
Copy Markdown
Member

Summary

Follow-up to #735, found by the before/after battery on the DAG engine review (L1 test station, wired gigabit, 47 ms RTT to the lab): a 5000 x 4 KiB get -r --parallel 4 on S3 went from 68 s to 127 s on the after binary while rclone held at 60 s in the same window. Integrity checks passed on both sides, so it was a correct transfer that got slower. The delta per file multiplied by the parallelism gives 47.2 ms on that cell and 54.9 ms on a 166-file mixed cell, against a measured ICMP RTT of 47.4 ms: one extra round trip per file.

Cause. --multi-thread-streams now defaults to 4 (#735). Four providers decide whether to split a download by asking the server for the size first, gated only on streams >= 2: S3 sends a signed HEAD, Backblaze B2 a stat, WebDAV and Koofr a Range: bytes=0-0 probe GET. Before #735 the default was 1 stream, so the probe never ran; after it, every file paid it, including the thousands nowhere near the 250 MiB cutoff. FTP and SFTP read the size before their single-stream path anyway and were not affected.

Fix, class not instance. The callers already know the size: the batch executor from the listing entry, sync from its plan, a single get from its own stat. They pass it through a new StorageProvider::download_with_size_hint(remote, local, size_hint, progress) whose default is plain download, so the other 27 providers are untouched. The four probing providers implement it and skip the probe when the hint is below the cutoff (multi_thread::size_hint_rules_out_ranges, one rule for all four); an unknown size (None) still probes, so callers that do not know keep today's behaviour and a large file still gets its honest 206 check before the range split.

The governor charge on the download loops (also new in #735) was checked and is not involved: with no cap set it reads one lock and returns, once per response chunk.

Tests

  • multi_thread::tests::a_size_hint_rules_out_ranges_only_when_known_and_below_the_cutoff: the shared rule.
  • multi_thread::tests::a_known_small_size_returns_fallback_without_a_probe_request: counting HTTP fixture, known small size returns Fallback with the progress callback and 0 requests; unknown size probes once.
  • s3::tests::a_known_size_below_the_cutoff_skips_the_multi_stream_head_probe: counting fixture, 4 KiB file with streams 4: known size, 0 HEAD and the file lands; unknown size, 1 HEAD.

Both fixtures were run with the gates removed and failed there (0 expected, 1 observed).

Verified: cargo fmt, cargo clippy --all-targets -- -D warnings (rc 0), the multi_thread, S3, executor and single-file DAG test groups (95 passed).

Numbers to expect

A third bracket on the same cell is queued on the test station once this lands. Expected: the S3 5000-file download back at the before level (about 68 s on that link) with the multi-stream default kept.

Summary by CodeRabbit

  • Performance

    • Multi-file downloads avoid unnecessary file-size checks when the size is already known.
    • Transfer connections are refreshed periodically during large batches to improve reliability.
    • Speed limits now apply consistently across supported cloud-provider uploads and downloads.
  • File Metadata

    • S3 uploads preserve the source file’s modification time, including during server-side copies.
  • Compatibility

    • S3 listings continue to display upload time while retaining modification-time metadata separately.

…ti-stream decision

With the multi-stream download default of 4, S3 asked HEAD, Backblaze B2 asked a stat, and WebDAV and Koofr sent a Range 0-0 probe before every single download, to learn a size and compare it with the 250 MiB cutoff. On a batch that is one extra round trip per file, most of them for files nowhere near the cutoff: on a 47 ms link a 5000 x 4 KiB get -r on S3 went from 68 s to 127 s while rclone held at 60 s, and the delta per file times the parallelism recovers the link RTT in two independent cells.

The batch executor, sync and a single get already hold the size from the listing or their own stat. They now pass it through download_with_size_hint, a trait method with a plain-download default, and the four providers skip the probe when the hint is below the cutoff; an unknown size still probes, so callers that do not know keep today's behaviour. FTP and SFTP read the size before their single-stream path anyway and were not affected.

Tests: the shared rule, an HTTP fixture counting probe requests for the Range helper, and an S3 fixture counting HEADs (known small size: 0 HEAD; unknown size: 1). Both fixtures fail with the gates removed.
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: e7afe8ee-60e3-4a79-8305-2fc18e85f1a4

📥 Commits

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

📒 Files selected for processing (11)
  • CHANGELOG.md
  • src-tauri/src/bin/aeroftp_cli.rs
  • src-tauri/src/provider_transfer_executor.rs
  • src-tauri/src/providers/b2.rs
  • src-tauri/src/providers/koofr.rs
  • src-tauri/src/providers/mod.rs
  • src-tauri/src/providers/multi_thread.rs
  • src-tauri/src/providers/s3.rs
  • src-tauri/src/providers/webdav.rs
  • src-tauri/src/sync.rs
  • src-tauri/src/transfer_dag_single_file.rs

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


📝 Walkthrough

Walkthrough

The download flow now accepts optional file-size hints. Callers pass known sizes to providers, which skip range-size probes for files below the configured multi-thread cutoff. S3, B2, Koofr, and WebDAV implement the optimization with regression tests.

Changes

Download size hint optimization

Layer / File(s) Summary
Size hint contract and probe rules
src-tauri/src/providers/mod.rs, src-tauri/src/providers/multi_thread.rs
StorageProvider adds download_with_size_hint. Range downloads use known_size and skip probes when the size is below the cutoff.
Download caller propagation
src-tauri/src/bin/aeroftp_cli.rs, src-tauri/src/provider_transfer_executor.rs, src-tauri/src/sync.rs, src-tauri/src/transfer_dag_single_file.rs
Download paths pass known positive file sizes and preserve None when the size is unavailable.
Provider range-download integration
src-tauri/src/providers/{b2,koofr,s3,webdav}.rs
Providers accept size hints, skip range selection and metadata probes for undersized files, and pass known sizes to concurrent requests.
Probe optimization validation
src-tauri/src/providers/multi_thread.rs, src-tauri/src/providers/s3.rs, CHANGELOG.md
Tests cover cutoff boundaries, skipped probes for known small files, and probing for unknown sizes. The changelog records the optimization.

Priority: ⬇️ Low — Impact reflects low issue severity.

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

Severity of issue fixed: Low

Merge Risk: ⚪ Minimal · up to fd5d5

Downloads with known small file sizes avoid unnecessary provider round trips while preserving existing behavior for unknown sizes and other providers. No current merge-blocking risk is identified.

Sequence Diagram(s)

sequenceDiagram
  participant DownloadCaller
  participant StorageProvider
  participant MultiThreadDownloader
  DownloadCaller->>StorageProvider: download_with_size_hint(size_hint)
  StorageProvider->>MultiThreadDownloader: evaluate size_hint against cutoff
  MultiThreadDownloader-->>StorageProvider: skip probe or perform range probe
  StorageProvider-->>DownloadCaller: download result
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 20 functions across 9 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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: avoiding one server round trip per file during multi-stream download decisions.
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 20 functions across 9 files. (2 skipped: 1 unsupported, 1 too large.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/multi-thread-probe-size-hint

Warning

Some tools did not complete. Review the errors below.

🔧 ast-grep (0.45.2)
src-tauri/src/bin/aeroftp_cli.rs

ast-grep timed out on this file


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 7336fa6 into main Sep 8, 2026
19 checks passed
@axpnet
axpnet deleted the fix/multi-thread-probe-size-hint branch September 8, 2026 11:05
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