fish2018/pansou · error

重试 次后仍然失败

Error message

重试 %d 次后仍然失败: %w

What it means

doRequestWithRetry exhausts maxRetries attempts (closing any non-nil response body each time, recording lastErr) and returns a wrapped error with the retry count. This is the aggregate failure of every attempt; the original cause is inside the wrapped lastErr (timeout, connection refused, bad status, etc.).

Solutions

  1. Unwrap lastErr (errors.Unwrap / %w chain) to identify the root cause — timeout vs refused vs status
  2. If context deadline exceeded, increase the request timeout or retry budget
  3. If connection refused/DNS errors, fix or update the source base URL
  4. If rate-limited, add backoff between retries or use a different egress IP/proxy

Example fix

// before
return nil, fmt.Errorf("重试 %d 次后仍然失败: %w", maxRetries, lastErr)
// after
return nil, fmt.Errorf("重试 %d 次后仍然失败: %w", maxRetries, lastErr) // caller: errors.Is(err, context.DeadlineExceeded) to branch
Defensive patterns

Strategy: retry

Validate before calling

if err := net.DialTimeout("tcp", host+":443", 5*time.Second); err != nil {
    // host unreachable; skip before invoking retry loop
}

Type guard

func isRetryExhausted(err error) bool {
    return strings.Contains(err.Error(), "重试") && strings.Contains(err.Error(), "仍然失败")
}

Try / catch

if err != nil {
    if isRetryExhausted(err) {
        root := errors.Unwrap(err) // lastErr: the real cause
        log.Printf("all retries failed, root cause: %v", root)
        return fallbackSource.Search(ctx, kw)
    }
    return err
}

Prevention

When it happens

Trigger: All maxRetries attempts of the HTTP request fail — every attempt returns a transport error (dial/timeout/TLS) or an error status the retry loop treats as failure, and the loop falls through to the final return.

Common situations: Upstream site down for the entire retry window, IP rate-limited or blocked so every attempt fails, DNS dead for a defunct mirror, network egress disabled in the deployment environment.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07). Data as JSON: /api/errors/61a6fbeae14d1d7a. Report an issue: GitHub.

Appendix: source

Thrown at plugin/kkv/kkv.go:409

	for i := 0; i < maxRetries; i++ {
		if i > 0 {
			backoff := time.Duration(1<<uint(i-1)) * 200 * time.Millisecond
			time.Sleep(backoff)
		}
		
		reqClone := req.Clone(req.Context())
		resp, err := client.Do(reqClone)
		if err == nil && resp.StatusCode == 200 {
			return resp, nil
		}
		
		if resp != nil {
			resp.Body.Close()
		}
		lastErr = err
	}
	
	return nil, fmt.Errorf("重试 %d 次后仍然失败: %w", maxRetries, lastErr)
}

View on GitHub (pinned to beaa561337)