fish2018/pansou · error
重试 次后失败
Error message
重试 %d 次后失败: %w
What it means
doJuPansouRequestWithRetry returns this after jupansouMaxRetries attempts all failed, wrapping lastErr (either the last transport error or the last non-2xx status error). It is the terminal failure of the HTTP fetch step for the JuPansou search and propagates to searchImpl's "请求失败" wrapper.
Solutions
- Unwrap the chain to see the last attempt's cause (errors.Unwrap twice: retry wrapper → status/transport error).
- Test the endpoint manually with curl from the same host.
- Increase jupansouMaxRetries or the backoff base if the outage is brief.
- Apply exponential backoff with jitter and circuit-breaking to avoid hammering a downed upstream.
- Check for IP-level bans; rotate egress IP or use a proxy.
Example fix
// before: fixed retry count, no jitter time.Sleep(200 * time.Millisecond * time.Duration(1<<attempt)) // after: add jitter to avoid thundering-hered retries time.Sleep(time.Duration(float64(200*time.Millisecond) * (1 << attempt) * (0.5 + rand.Float64())))
Defensive patterns
Strategy: retry
Try / catch
results, err := p.searchImpl(client, keyword)
if err != nil && strings.Contains(err.Error(), "重试") {
log.Printf("jupansou exhausted retries: %v", err)
results = cache.Get(keyword) // stale fallback
} Prevention
- Use exponential backoff with jitter and an upper bound on total wait.
- Cache last-good results to survive full retry exhaustion.
- Circuit-break a source after repeated exhaustion and re-enable it after a cooldown.
- Monitor retry-exhaustion rates per source to detect upstream degradation early.
When it happens
Trigger: Every retry attempt in the loop failed — persistent connection errors, timeouts, or repeated non-2xx status codes — with exponential backoff (200ms<<attempt) between attempts, until the loop ends.
Common situations: Sustained upstream outage; IP blocked or rate-limited for the entire retry window; DNS failures in the deployment environment; timeout too short for every attempt.
Understand the failure class
Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/c200fd61857aace8.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/jupansou/jupansou.go:340
func doJuPansouRequestWithRetry(req *http.Request, client *http.Client) (*http.Response, error) {
var lastErr error
for attempt := 0; attempt < jupansouMaxRetries; attempt++ {
resp, err := client.Do(req.Clone(req.Context()))
if err == nil && resp.StatusCode == http.StatusOK {
return resp, nil
}
if resp != nil {
resp.Body.Close()
}
lastErr = err
if lastErr == nil {
lastErr = fmt.Errorf("HTTP 状态码 %d", resp.StatusCode)
}
if attempt < jupansouMaxRetries-1 {
time.Sleep(200 * time.Millisecond * time.Duration(1<<attempt))
}
}
return nil, fmt.Errorf("重试 %d 次后失败: %w", jupansouMaxRetries, lastErr)
}
View on GitHub (pinned to beaa561337)