GopeedLab/gopeed · error
connection %d failed: retries=%d
Error message
connection %d failed: retries=%d
What it means
The fallback branch of the same aggregation: a connection is flagged connFailed and failed, but lastErr is nil, so no cause can be reported. The download still failed; the reason was simply never recorded by whichever code path marked the connection failed.
Source
Thrown at internal/protocol/http/fetcher.go:1541
// If total downloaded matches file size, consider it a success regardless of connection failures
downloadComplete := f.meta.Res.Size > 0 && totalDownloaded >= f.meta.Res.Size
// Check for any errors, but ignore 403 (server connection limit) errors if download completed
var finalErr error
if !downloadComplete && !allChunksComplete {
for _, conn := range f.connections {
if conn.State == connFailed && conn.failed {
// Skip 403 errors (server connection limit) - these are expected when exceeding server's limit
if re := extractRequestError(conn.lastErr); re != nil && re.Code == 403 {
continue
}
if re := extractRequestError(conn.lastErr); re != nil {
finalErr = fmt.Errorf("connection %d failed: retries=%d, status=%d", conn.ID, conn.retryTimes, re.Code)
} else if conn.lastErr != nil {
finalErr = fmt.Errorf("connection %d failed: retries=%d, err=%v", conn.ID, conn.retryTimes, conn.lastErr)
} else {
finalErr = fmt.Errorf("connection %d failed: retries=%d", conn.ID, conn.retryTimes)
}
break
}
}
}
f.connMu.Unlock()
// Close the file before signaling completion
// This ensures the file handle is released before Wait() returns
f.fileMu.Lock()
if f.file != nil {
f.file.Close()
f.file = nil
}
f.fileMu.Unlock()
if finalErr != nil {
f.setState(stateError)View on GitHub (pinned to 7b7327ffb3)
Solutions
- Reproduce with verbose/debug logging for the fetcher and its connections to recover the missing cause
- Treat it operationally like the transport-error variant: fresh Fetcher, retry with backoff
- If reproducible, inspect the code paths that mark connections failed and ensure they store lastErr (this message existing means one does not)
- Check whether the task was cancelled or paused concurrently — cancellation races can leave connections in a bare failed state
Example fix
// before
if err := fetcher.Wait(); err != nil { return err } // "connection 1 failed: retries=6" — no cause
// after
if err := fetcher.Wait(); err != nil {
log.Printf("download failed (cause not captured): %v", err)
return retryFreshFetcher(req, opts, 2) // rebuild fetcher; keep logs for diagnosis
} Defensive patterns
Strategy: retry
Try / catch
if err := fetcher.Wait(); err != nil {
msg := err.Error()
if strings.Contains(msg, "failed: retries=") && !strings.Contains(msg, "err=") && !strings.Contains(msg, "status=") {
log.Printf("download failed without a recorded cause: %v", msg)
}
return retryFresh(req, opts, 2)
} Prevention
- Enable verbose connection logging before you need it — this variant carries no cause
- Avoid cancelling/pausing downloads concurrently with retries; races can strip the error
- If reproducible, report it: a failed connection should always record lastErr
When it happens
Trigger: A code path (or race) that sets State = connFailed and failed = true without assigning lastErr — for example a panic-recovered worker or a stop-path that reuses the failure flag. From the caller's view it looks like a failure with no network or status cause.
Common situations: Rare in practice; when it appears it usually accompanies context cancellation racing the retry logic, or a bug in custom builds. Because the cause is missing, diagnosis needs connection-level logging enabled beforehand.
Related errors
- connection %d failed: retries=%d, status=%d
- connection %d failed: retries=%d, err=%v
- promise rejected
- promise rejected
- invalid resource file name
AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16).
Data as JSON: /api/errors/6eb059dcbe8496ba.
Report an issue: GitHub.