Skip to content

feat(web): supersede a stuck upload and complete zero-byte files#1381

Merged
Benoît Cortier (CBenoit) merged 1 commit into
Devolutions:masterfrom
ymarcus93:yuval/upload-supersede-and-empty-files
Jun 23, 2026
Merged

feat(web): supersede a stuck upload and complete zero-byte files#1381
Benoît Cortier (CBenoit) merged 1 commit into
Devolutions:masterfrom
ymarcus93:yuval/upload-supersede-and-empty-files

Conversation

@ymarcus93

Copy link
Copy Markdown
Contributor

Problem

#1372 made RdpFileTransferProvider recover uploadState when a paste is interrupted or never
pulled (paste-ack + inactivity watchdogs), so a stuck paste no longer wedges every later upload. But
recovery is only automatic and only after the ~60s watchdog windows. Two gaps remain:

  • A stuck upload still hard-blocks a new one. uploadFiles() throws Upload already in progress
    while uploadState is set. So a user who cancels the copy on the remote (no CLIPRDR signal for
    that) or hits a stalled paste can't just retry — they wait for a watchdog. There's also no way for
    a host to ask whether an upload is in progress, so hosts track a parallel flag that can drift
    from uploadState.
  • Zero-byte files never complete. A file is marked complete when its bytes are served (the RANGE
    onload). A 0-byte file has no bytes, so the remote requests only FILECONTENTS_SIZE and never a
    FILECONTENTS_RANGE — the file is counted in expectedFileCount but never added to
    completedFiles, the batch never finishes, and uploadState sticks (which fix(web): recover file-upload state when a paste is interrupted or never pulled #1372's watchdog then
    fails after ~60s). Pasting a folder containing empty files hangs.

Fix

A natural extension of #1372's recovery model — same uploadState lifecycle, two more terminal
paths:

  • Supersede instead of refuse. When uploadState is already set, uploadFiles() tears the stale
    batch down (new supersedeUpload(): abort in-flight reads, clear fix(web): recover file-upload state when a paste is interrupted or never pulled #1372's watchdogs, resolve the
    old completion — no error, since it's a replacement, not a failure) and advertises the new files.
    A new Format List supersedes the previous offer anyway per [MS-RDPECLIP] 3.1.1.1. The superseding
    upload re-arms fix(web): recover file-upload state when a paste is interrupted or never pulled #1372's watchdogs, so all of its recovery still applies. suppressUploadMonitoring
    is made idempotent for the old→new handoff. The in-flight-read teardown shared by supersedeUpload,
    failPendingUpload, and dispose is factored into one abortInFlightReads() helper (was copied in
    all three).
  • isUploadInProgress(): boolean — small getter (uploadState !== undefined) so a host can use
    the provider as the single source of truth for its own one-upload-at-a-time decision (covering both
    host uploads and remote re-pastes), and decide when to supersede.
  • Complete zero-byte files. A 0-byte file gets a FILECONTENTS_SIZE request but never a RANGE, so
    in the SIZE branch it's marked complete right after replying — mirroring the download path's existing
    size === 0 handling. The "mark one file complete" step (record it, emit upload-complete once,
    finishUploadBatch when the batch is fully accounted) is a single markUploadFileComplete() helper
    shared by the RANGE-onload path and this SIZE path, so the two can't drift. Purely client-side; the
    protocol already delivers the SIZE request.

Where #1372 recovers a stuck upload automatically, this lets a new paste replace it immediately —
and stops empty files from getting stuck in the first place.

Scope

  • iron-remote-desktop-rdp (web) only. No ironrdp-cliprdr / ironrdp-web / binding changes. Builds
    on fix(web): recover file-upload state when a paste is interrupted or never pulled #1372. The internal helpers (markUploadFileComplete, abortInFlightReads) are pure
    refactors that de-duplicate existing logic — no behavior change from them.
  • Public API addition: isUploadInProgress(): boolean.
  • Behavioral change to uploadFiles() — note for existing callers: it no longer throws
    Upload already in progress when a batch is already in flight; it supersedes the prior batch and
    resolves that batch's completion. A caller that wrapped uploadFiles() in a try/catch for that
    specific error can drop it — the call now always starts the new batch. Callers that want
    one-upload-at-a-time semantics should gate on the new isUploadInProgress() and decide whether to
    refuse or supersede (this is exactly what the getter enables). No other behavior changes.

Tests

RdpFileTransferProvider.test.ts:

  • isUploadInProgress across a lifecycle (idle → advertised → complete), released after a stalled
    failure, and true again during a re-paste.
  • Superseding an in-flight upload resolves the old completion and advertises a new batch in its
    place.
  • A lone 0-byte file completes from its FILECONTENTS_SIZE request alone (no RANGE); a 0-byte file
    finishes a mixed batch only after the data files are served.
  • The two fix(web): recover file-upload state when a paste is interrupted or never pulled #1372 tests that asserted the Upload already in progress throw now assert
    isUploadInProgress() (the supersede behavior).

Copilot AI 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.

Pull request overview

This PR updates the web RdpFileTransferProvider (in iron-remote-desktop-rdp) to make uploads more resilient: starting a new upload now supersedes any in-progress batch, and empty (0-byte) files can complete without ever receiving a RANGE request—preventing the provider’s uploadState from becoming stuck.

Changes:

  • Change uploadFiles() to supersede an existing upload batch instead of throwing, and add isUploadInProgress() as a source-of-truth getter for hosts.
  • Fix 0-byte uploads by marking files complete on the FILECONTENTS_SIZE path, and factor completion/teardown logic into shared helpers.
  • Update/extend unit tests to cover supersede behavior, isUploadInProgress(), and 0-byte completion scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
web-client/iron-remote-desktop-rdp/src/RdpFileTransferProvider.ts Implements supersede-on-new-upload, adds isUploadInProgress(), fixes 0-byte completion, and refactors teardown/completion logic.
web-client/iron-remote-desktop-rdp/src/RdpFileTransferProvider.test.ts Adds coverage for supersede behavior, upload-in-progress lifecycle, and zero-byte completion (single and mixed batches).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread web-client/iron-remote-desktop-rdp/src/RdpFileTransferProvider.ts

@CBenoit Benoît Cortier (CBenoit) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It may be worth to address Copilot’s comment, but otherwise a sensible patch, LGTM!

@ymarcus93 Yuval Marcus (ymarcus93) force-pushed the yuval/upload-supersede-and-empty-files branch from 3245a70 to f4e1ea5 Compare June 23, 2026 18:06
@ymarcus93

Copy link
Copy Markdown
Contributor Author

It may be worth to address Copilot’s comment, but otherwise a sensible patch, LGTM!

Good catch! I pushed an amended commit with the fix a new test for it as well

@CBenoit Benoît Cortier (CBenoit) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Perfect, thank you!

@CBenoit Benoît Cortier (CBenoit) enabled auto-merge (squash) June 23, 2026 18:22
@CBenoit Benoît Cortier (CBenoit) merged commit f12bbce into Devolutions:master Jun 23, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants