fix(desktop): make the updater download resilient — retry, resume, IPv4 fallback#4141
Merged
Conversation
A single mid-stream connection reset — common on CN IPv6 routes to Cloudflare — failed the whole self-update with a raw "forcibly closed" error. Retry the manifest, asset, and signature fetches up to 3 times with a short backoff so a transient RST self-recovers instead of surfacing.
Build on the download retry: a retry now resumes from the bytes already received via an HTTP Range request instead of restarting the tens-of-MB installer from zero, and switches to an IPv4-pinned client — a first-try reset usually means the IPv6 route (CN to Cloudflare) is the problem. netclient gains a ForceIPv4 transport option for the fallback.
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.
Makes the desktop self-updater survive flaky links — specifically the CN IPv6 → Cloudflare routes that reset mid-transfer and surfaced as:
The updater did a single GET for the manifest, the installer asset, and the signature, and gave up on the first reset.
Changes
Retry transient transport failures —
retryTransientwraps the manifest, asset, and signature fetches and retries up to 3 times with a short backoff (0.5s, 1s), bailing immediately on context cancellation. Signature + sha256 verification stay downstream and terminal, so a corrupt body is never retried into a "success".Resume instead of restart — on a retry,
downloadsends aRange: bytes=<n>-request and continues from the bytes already received rather than re-pulling the whole tens-of-MB installer. A206carries the remainder (total taken fromContent-Range); a200means the server ignoredRange, so we reset and re-download in full.IPv4 fallback — a first-try reset usually means the IPv6 route is the problem, so retries switch to an IPv4-pinned client.
netclient.TransportOptionsgains aForceIPv4flag that pins the dialer totcp4(preserving the default 30s dial/keep-alive). Off by default — no behavior change for any existing caller.Tests
TestDownloadRecoversFromMidStreamReset— server truncates the body mid-stream for the first N-1 attempts, then serves the full body; download recovers.TestDownloadResumesWithRange— first attempt sends a prefix then drops the socket; the retry sendsRange: bytes=200-, the server replies206, and the assembled bytes equal the original file.TestDownloadFallsBackToSecondClient— primary client always errors; download completes via the fallback client.TestDownloadGivesUpAfterCap/TestRetryTransientStopsWhenCancelled— persistent failure stops after the cap; a cancelled context short-circuits after one attempt.TestForceIPv4Dials(netclient) — the forced-IPv4 transport dials a v4 listener and rejects an IPv6 literal.All updater + netclient tests pass locally (
go vetclean). The race detector runs in CI (local box has no cgo/gcc).