fish2018/pansou · error
重试 次后失败
Error message
重试 %d 次后失败: %w
What it means
doRequestWithRetry performs HTTP requests with exponential backoff (retryBaseDelay * 2^attempt) for up to maxRetries attempts. If every attempt fails, it returns this error wrapping the last underlying error with %w. It aggregates persistent transport failures after exhausting the retry budget.
Solutions
- Inspect the wrapped lastErr (errors.Unwrap / %w chain) to find the true root cause (timeout, connection refused, TLS).
- Confirm network egress and DNS resolution from the host running the plugin.
- Increase maxRetries or retryBaseDelay if the upstream is intermittently slow or rate limiting.
- Retry later if the target site is temporarily down; consider a proxy or alternate mirror.
Example fix
// before
res, err := p.doRequestWithRetry(client, url)
if err != nil { return err }
// after
res, err := p.doRequestWithRetry(client, url)
if err != nil {
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
return fmt.Errorf("upstream timeout, try increasing client timeout: %w", err)
}
return err
} Defensive patterns
Strategy: retry
Validate before calling
// pre-flight reachability check
if _, err := net.LookupTimeout(ctx, "tcp", host+":443", 5*time.Second); err != nil {
return fmt.Errorf("host unreachable before request: %w", err)
} Try / catch
if err != nil {
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
// schedule retry with longer backoff
}
return fmt.Errorf("all retries exhausted: %w", err)
} Prevention
- Tune maxRetries and retryBaseDelay to the upstream's failure profile.
- Monitor site availability before running bulk scrape jobs.
- Always inspect the wrapped root error, not just the wrapper message.
- Use proxies/circuit breakers for flaky upstreams.
When it happens
Trigger: Any call chain through searchImpl or fetchDetailLinks that reaches doRequestWithRetry while the target host is unreachable, times out, resets connections, or returns repeatedly failing responses on all maxRetries attempts.
Common situations: Site is down or blocking the client; DNS failure in the deployment environment; timeout too aggressive for a slow upstream; corporate proxy/firewall blocking outbound requests; retryBaseDelay/backoff insufficient during rate limiting.
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/b8201fa86b74bf57.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/daishudj/daishudj.go:436
}
func (p *DaishuPlugin) doRequestWithRetry(req *http.Request, client *http.Client) (*http.Response, error) {
var lastErr error
for attempt := 0; attempt < maxRetries; 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 attempt < maxRetries-1 {
backoff := retryBaseDelay * time.Duration(1<<attempt)
time.Sleep(backoff)
}
}
return nil, fmt.Errorf("重试 %d 次后失败: %w", maxRetries, lastErr)
}
func startCacheCleaner() {
ticker := time.NewTicker(cacheCleanupInterval)
defer ticker.Stop()
for range ticker.C {
now := time.Now()
detailCache.Range(func(key, value interface{}) bool {
entry, ok := value.(cacheEntry)
if !ok || now.After(entry.expiresAt) {
detailCache.Delete(key)
}
return true
})
}
}
View on GitHub (pinned to beaa561337)