{"record":{"id":"545ea0aef6f24d69","repo":"GopeedLab/gopeed","slug":"connection-d-failed-retries-d-status-d","errorCode":null,"errorMessage":"connection %d failed: retries=%d, status=%d","messagePattern":"connection (.+?) failed: retries=(.+?), status=(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/protocol/http/fetcher.go","lineNumber":1537,"sourceCode":"\t\t\tallChunksComplete = false\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// If total downloaded matches file size, consider it a success regardless of connection failures\n\tdownloadComplete := f.meta.Res.Size > 0 && totalDownloaded >= f.meta.Res.Size\n\n\t// Check for any errors, but ignore 403 (server connection limit) errors if download completed\n\tvar finalErr error\n\tif !downloadComplete && !allChunksComplete {\n\t\tfor _, conn := range f.connections {\n\t\t\tif conn.State == connFailed && conn.failed {\n\t\t\t\t// Skip 403 errors (server connection limit) - these are expected when exceeding server's limit\n\t\t\t\tif re := extractRequestError(conn.lastErr); re != nil && re.Code == 403 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif re := extractRequestError(conn.lastErr); re != nil {\n\t\t\t\t\tfinalErr = fmt.Errorf(\"connection %d failed: retries=%d, status=%d\", conn.ID, conn.retryTimes, re.Code)\n\t\t\t\t} else if conn.lastErr != nil {\n\t\t\t\t\tfinalErr = fmt.Errorf(\"connection %d failed: retries=%d, err=%v\", conn.ID, conn.retryTimes, conn.lastErr)\n\t\t\t\t} else {\n\t\t\t\t\tfinalErr = fmt.Errorf(\"connection %d failed: retries=%d\", conn.ID, conn.retryTimes)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tf.connMu.Unlock()\n\n\t// Close the file before signaling completion\n\t// This ensures the file handle is released before Wait() returns\n\tf.fileMu.Lock()\n\tif f.file != nil {\n\t\tf.file.Close()\n\t\tf.file = nil\n\t}","sourceCodeStart":1519,"sourceCodeEnd":1555,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/internal/protocol/http/fetcher.go#L1519-L1555","documentation":"Produced in onDownloadComplete (internal/protocol/http/fetcher.go:1537) when the download did not finish and at least one connection ended in connFailed after exhausting its retries. This variant is chosen when the connection's last error is a RequestError carrying an HTTP status code; that code is included in the message. 403 is deliberately skipped because it usually signals a server-side per-IP connection limit, not a real failure.","triggerScenarios":"A chunk connection repeatedly received 404 (file removed mid-download), 416 (range no longer satisfiable after the file changed), 500/502/503 (server errors), or 401 (signed URL expired) until the retry budget ran out.","commonSituations":"Expired pre-signed URLs on CDN hosts, servers that reject many parallel range requests, files deleted or replaced while a long download was paused, or a proxy stripping Range support so chunked workers fail.","solutions":["Match the status: 404/410 means the URL is gone — re-resolve the resource; 401/403 with signed URLs means re-sign; 5xx means retry later","Verify with a HEAD/GET that the URL still returns 200 and Accept-Ranges: bytes before retrying a multi-connection download","Reduce the connection count (some servers throttle parallel ranges)","Retry the whole task from a fresh Fetcher instead of re-Start"],"exampleFix":"// before\nerr := fetcher.Wait() // \"connection 2 failed: retries=5, status=416\"\n\n// after\n// preflight before a multi-connection retry\nresp, err := http.Head(url)\nif err == nil && resp.StatusCode == 200 && resp.Header.Get(\"Accept-Ranges\") == \"bytes\" {\n    // safe to retry with range connections\n} else {\n    // fall back to a single connection or re-resolve the URL\n}","handlingStrategy":"retry","validationCode":"// Preflight the URL before a multi-connection download\nfunc supportsRanges(url string) bool {\n    resp, err := http.Head(url)\n    if err != nil || resp.StatusCode != http.StatusOK {\n        return false\n    }\n    return resp.Header.Get(\"Accept-Ranges\") == \"bytes\"\n}","typeGuard":null,"tryCatchPattern":"err := fetcher.Wait()\nif err != nil && strings.Contains(err.Error(), \"failed: retries=\") && strings.Contains(err.Error(), \"status=\") {\n    // extract the status and decide: re-resolve for 401/404, backoff-retry for 5xx\n    return retryFresh(req, opts, 3) // new Fetcher per attempt\n}","preventionTips":["Check Accept-Ranges and a 200 status before enabling chunked/multi-connection mode","For signed URLs, ensure TTL comfortably exceeds the expected download time","Keep the connection count modest on hosts that throttle parallel ranges"],"tags":["http","download","connection","retries","status-code"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}