Repository navigation
fix(cli): resume a cloud download part when its connection drops - #5873
Merged
Merged
Conversation
A ranged part that loses its connection now re-requests only the bytes it is missing, up to 3 retries with backoff, then fails naming the byte range. A server that ignores Range still fails fast: there is nothing to resume from. The test server no longer closes idle keep-alive sockets, which closed a socket under a slow reader on Windows.
jrusso1020
approved these changes
Oct 19, 2026
jrusso1020
left a comment
Collaborator
There was a problem hiding this comment.
Approving at 8559dd09.
- Resume is byte-exact.
pumpwrites each chunk atpart.atand then advances it, so a retry asks forbytes=<part.at>-<end>and never rewrites or double-counts bytes it already has (bytes/onProgressstay right). A body that ends short throwsconnection closed at byte Nand resumes from there. - Cancel and sibling failure still stop everything.
if (signal.aborted) throw errorandsleep(..., { signal })both use the combinedfailFast+ user signal. ThecatchondownloadToFilestill rethrows the user's own abort reason. - No-Range fallback keeps the old fail-fast truncation check unchanged.
- The test fix matches the evidence. The error recorded one complete 7,789,629-byte response before the socket closed, which fits the 5 s keep-alive idle timer.
keepAliveTimeout = 0addresses that, andserver.close()still cleans up at teardown. The newdrop: { at, times }helper also checks that the resume request starts at the cut byte. - CI: nothing red at this head; the rest is pending.
Non-blocking:
- Every error in a ranged part is retried, not just a dropped connection. An expired presigned URL (403) or a
range ignoredreply now waits 1.75 s and then reportsbytes X-Y dropped 4 times. The HTTP status survives only incause, and the ranged path no longer shows the old "presigned URL may have expired — refetch viahyperframes cloud get" hint. It might be worth failing straight through on!res.ok/range ignored, or naming the last status in the message. - The per-part
written !== lengthcheck is gone for ranged parts. A server that sends more than the requested range now writes pastendinto the next part's region instead of erroring. The bytes would normally be identical, so this is theoretical.
Need: yes, fixes the Windows main failure, and real servers drop connections too · Size: ok (+70/−26) · Reuse: same get/assertOk/failFast machinery · Simpler: no, this is about as small as resume gets.
No live run. I didn't run the tests locally.
— Rames
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
hyperframes cloud render/cloud getdownloads the output in parallel ranged parts. Until now one part losing its connection failed the whole download. Now:bytes=<next>-<end>), up to 3 retries with 0.25 / 0.5 / 1 s backoff. After that it fails withFailed to download <url>: bytes <start>-<end> dropped 4 times(the last error is the cause). The previous output is kept and no staged file is left behind, as before.Content-Range) still fails fast. Without ranges there is nothing to resume from, and retrying from zero would redownload the whole file.Why the test failed on Windows main
splits a large file into parallel ranged requestsfailed on the Windows lane withTypeError: terminated, caused bySocketError: other side closed, after 16.8 s. The socket info in the error shows one request written (205 bytes) and one complete response read (7,789,629 bytes = headers + the 7,789,435-byte last part). So the server closed a connection after sending everything, while the client was still writing that body to disk.Inference (not reproduced on macOS or Linux): the Node test server's keep-alive idle timer (5 s by default) closed the socket. Windows completes a large loopback write at once, so the idle clock starts right away, while the test took 16.8 s under the parallel suite. On macOS and Linux the write only finishes as the reader drains, so the timer never wins.
Fix at the cause: the test server sets
keepAliveTimeout = 0, so it never closes a socket during a test (server.close()still closes idle sockets at teardown). The client fix means a real server closing a connection early is now survived as well.Tests
resumes a range whose connection drops mid-body from the bytes it already has. The server cuts the range covering one byte twice; the download completes byte-identical and the resume request starts at the cut byte.keeps the previous output and leaves no staged file when one part keeps dropping. The range always drops, the error saysdropped 4 times, and the old output survives.download.tsboth fail with the sameterminated/other side closedas the Windows failure. With the fix, the download suite passes 17/17, 3 runs in a row (Linux).