Skip to content

Deduplicate transfer managers into transfer_common and cap finished history#121

Merged
macnev2013 merged 10 commits into
macnev2013:mainfrom
iamnoksio:refactor/backend
Jul 25, 2026
Merged

Deduplicate transfer managers into transfer_common and cap finished history#121
macnev2013 merged 10 commits into
macnev2013:mainfrom
iamnoksio:refactor/backend

Conversation

@iamnoksio

@iamnoksio iamnoksio commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extracts duplicated transfer logic (progress tracking, speed/ETA computation, history capping) from the SFTP, SCP, and S3 managers into a new transfer_common module, and caps the finished-transfer history at 200 entries on both the Rust and TypeScript sides. It also upgrades S3 uploads to multipart and downloads to range-based fetching, makes SFTP directory-download stat walks asynchronous for faster UI appearance, and fixes the previously flagged DashMap re-entrancy deadlock and double record_finished call via an is_terminal() guard in set_job_status.

  • transfer_common: new shared record_progress (with EMA speed smoothing, SPEED_WINDOW shortened to 500 ms), record_finished with cap + eviction, apply_concurrency, eta_secs, and accompanying unit tests including a deadlock watchdog.
  • Rust managers: set_job_status now drops the DashMap shard guard before calling record_finished, and early-returns if the job is already terminal — resolving both the shard-lock reëntry deadlock and the duplicate history entry from queued-cancel racing the worker.
  • TypeScript store: finished_order tracks terminal transfer IDs in insertion order; trimFinished evicts the oldest terminal entry when the 200-entry cap is exceeded; a deduplication guard prevents the same ID from occupying two slots.

Confidence Score: 4/5

Safe to merge; all previously flagged deadlock and double-push paths are correctly guarded. One untreated gap: retry() does not remove the transfer ID from finished_order, leaking phantom history slots on repeated retries.

The three transfer managers each have a retry() function that resets a failed/cancelled job to Queued without removing its ID from finished_order. Each subsequent failure re-appends the ID, so N retries produce N−1 stale phantom entries that consume history slots without ever being cleaned up. With enough retries per job the effective history cap silently shrinks. All other logic—deadlock avoidance, terminal-state idempotency, TypeScript deduplication, and the Rust eviction tests—looks correct.

Files Needing Attention: retry() in src-tauri/src/sftp/transfer_manager.rs, src-tauri/src/s3/transfer_manager.rs, and src-tauri/src/scp/transfer_manager.rs each need a finished_order cleanup step.

Important Files Changed

Filename Overview
src-tauri/src/transfer_common.rs New shared module extracting progress tracking, ETA, speed (now with EMA smoothing), and history-capping logic. All previously-flagged deadlock and double-push paths are guarded correctly. Tests cover eviction, stale IDs, non-terminal guard, and a deadlock watchdog.
src-tauri/src/sftp/transfer_manager.rs Delegates progress and history to transfer_common; adds async stat-walk for dir downloads (starts at 0/0); upload now uses an aggregator channel for progress. retry() does not remove the ID from finished_order, leaving stale phantom entries per retry cycle.
src-tauri/src/s3/transfer_manager.rs Upgrades upload to multipart (8 MB chunks) and download to range-based fetching; integrates transfer_common. retry() has the same stale finished_order entry gap as SFTP.
src-tauri/src/scp/transfer_manager.rs Delegates to transfer_common; set_job_status now guards against duplicate terminal transitions with is_terminal(). retry() has the same stale finished_order entry gap.
src/stores/transfer-store.ts Adds finished_order array and 200-entry history cap; deduplication guard prevents the same transfer from occupying two slots on repeated terminal events; removeTransfer and clearFinished correctly maintain the order array.
src/stores/transfer-store.test.ts New test file covering duplicate terminal events, cap eviction, active-transfer protection, removeTransfer slot release, and clearFinished. Good coverage of the new finished_order invariants.
src-tauri/src/sftp/commands.rs Refactors sftp_chmod_recursive target-push to separate if/else branches for dirs and files; semantically equivalent to the original but clarifies intent.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([Job enqueued\nQueued]) --> B{cancel_token\ncancelled?}
    B -- yes, Queued --> C[set status = Cancelled\ndrop shard guard\nemit event]
    C --> RF1[record_finished\nfinished_order mutex\n→ push + evict]
    B -- no --> D[set status = InProgress\nworker runs]
    D --> E{Result}
    E -- Ok --> F[bytes_transferred = total\nset Completed]
    E -- TransferCancelled --> G[set Cancelled]
    E -- Err --> H[set Failed]
    F & G & H --> SJS[set_job_status\nacquire shard guard]
    SJS --> IT{is_terminal\nalready?}
    IT -- yes --> NOOP([return — no duplicate\nrecord_finished])
    IT -- no --> UPD[update status/error\ndrop shard guard\nemit event]
    UPD --> RF2[record_finished\nfinished_order mutex\n→ push + evict]
    RF2 --> DONE([Done])
    RF1 --> RETRY{retry\ncalled?}
    RETRY -- yes --> RST[reset to Queued\nNOT removed from\nfinished_order ⚠️]
    RST --> D
    RETRY -- no --> DONE
Loading

Reviews (3): Last reviewed commit: "test(transfers): pin finished-history in..." | Re-trigger Greptile

Improve UI responsiveness by enqueuing directory download jobs immediately with 0 totals instead of blocking to calculate them first. A background task now walks the remote directory and updates the job totals once complete. Also add warning logging in walk_remote_dir_inner for read_dir errors instead of silently returning.

Fixes macnev2013#111
Extract common transfer utilities (ETA calculation, progress tracking, concurrency management) from S3, SCP, and SFTP transfer managers into a new `transfer_common` module to reduce code duplication and improve maintainability.

Key improvements:
- Add EMA smoothing for speed calculation for more stable readings
- Reduce speed window from 2s to 500ms for faster UI responsiveness
- Implement ProgressFields trait to standardize progress field access
- Centralize ETA calculation and concurrency management logic
- Improve S3 multipart upload and range-based download
- Fix SFTP progress aggregation to use unbounded channel instead of direct calls
@iamnoksio
iamnoksio marked this pull request as draft July 21, 2026 01:03
Refactor S3, SCP, and SFTP transfer managers to eliminate code duplication by moving shared constants (EMIT_THROTTLE, MAX_FINISHED_HISTORY, SPEED_WINDOW) and the record_finished function to transfer_common. Introduce FinishedStatus trait to abstract terminal status checking, and simplify record_progress signature by using centralized constants instead of parameters.
@iamnoksio

Copy link
Copy Markdown
Contributor Author

Fixed #111

@iamnoksio
iamnoksio marked this pull request as ready for review July 23, 2026 06:29
Comment thread src-tauri/src/transfer_common.rs
Comment thread src/stores/transfer-store.ts Outdated
Comment thread src-tauri/src/sftp/transfer_manager.rs
Comment thread src/stores/transfer-store.ts
- set_job_status drops the DashMap write-guard before record_finished,
  which re-enters the map to evict old entries; a same-shard hit would
  deadlock once history passed the cap
- set_job_status treats terminal statuses as final, so a queued-cancel
  followed by the worker's dequeue (or a cancel racing the InProgress
  mark) can't emit or record a job twice
- updateTransfer skips finished_order ids already present, so repeated
  terminal events can't burn extra history slots
- drop the redundant last_emit stamp after record_progress (it already
  stamps on emit) and fix the MAX_FINISHED_HOSTORY typo
@macnev2013 macnev2013 changed the title Backend improvement Deduplicate transfer managers into transfer_common and cap finished history Jul 25, 2026
Rust: record_finished evicts only still-terminal jobs past the cap,
tolerates order ids missing from the map, and a watchdog test fails
(rather than hangs) if eviction ever re-enters the map while holding
a guard. Frontend: duplicate terminal events keep one history slot,
trimming never evicts active transfers, and remove/clear release
their order slots.
@macnev2013
macnev2013 merged commit 5641faa into macnev2013:main Jul 25, 2026
8 checks passed
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.

2 participants