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

  1. Reproduce with verbose/debug logging for the fetcher and its connections to recover the missing cause
  2. Treat it operationally like the transport-error variant: fresh Fetcher, retry with backoff
  3. If reproducible, inspect the code paths that mark connections failed and ensure they store lastErr (this message existing means one does not)
  4. 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

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


AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16). Data as JSON: /api/errors/6eb059dcbe8496ba. Report an issue: GitHub.