fish2018/pansou · error
重试 次后仍然失败
Error message
重试 %d 次后仍然失败: %w
What it means
This error is returned by doRequestWithRetry after all retry attempts (maxRetries) for an HTTP request have failed. It wraps the last error encountered (lastErr), so the true root cause (timeout, connection refused, reset) is preserved. Callers fetchSearchPage and fetchShareLink surface it wrapped in their own request-failed errors.
Solutions
- Inspect the wrapped lastErr to identify the root cause (timeout vs refused vs reset)
- Increase maxRetries and add exponential backoff with jitter between attempts
- Confirm upstream availability (curl/status page) and configure a proxy if the host is blocked in your network
- Verify http.Client settings (Timeout, Transport, proxy env vars)
- Fall back gracefully in callers — skip the pan/page instead of failing the whole search when retries are exhausted
Example fix
// before
return nil, fmt.Errorf("重试 %d 次后仍然失败: %w", maxRetries, lastErr)
// after
for attempt := 1; attempt <= maxRetries; attempt++ {
resp, err := client.Do(req)
if err == nil {
return resp, nil
}
if resp != nil {
resp.Body.Close()
}
lastErr = err
time.Sleep(backoffWithJitter(attempt)) // exponential backoff
}
return nil, fmt.Errorf("重试 %d 次后仍然失败: %w", maxRetries, lastErr) Defensive patterns
Strategy: retry
Validate before calling
// pre-flight reachability probe
conn, err := net.DialTimeout("tcp", "haisou.cc:443", 5*time.Second)
if err != nil {
return fmt.Errorf("upstream unreachable before request: %w", err)
}
conn.Close() Try / catch
_, err := p.doRequestWithRetry(req, client)
if err != nil {
var nerr net.Error
if errors.As(err, &nerr) && nerr.Timeout() {
// treat as upstream outage: back off longer before next attempt
}
return fmt.Errorf("request failed after retries: %w", err)
} Prevention
- Use exponential backoff with jitter between attempts
- Increase maxRetries modestly rather than hammering the upstream
- Verify proxy/DNS configuration in deployment environments
- Circuit-break and degrade gracefully when upstream is persistently down
When it happens
Trigger: doRequestWithRetry exhausts maxRetries attempts, each returning a transport-level error (timeout, connection refused, TLS failure, connection reset), and returns this error with the final attempt's cause.
Common situations: Upstream haisou.cc fully unreachable/down; aggressive rate limiting dropping all attempts; wrong proxy configuration; DNS failures in the deployment environment; retry window too short during a transient outage.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
Related errors
AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07).
Data as JSON: /api/errors/9f86f5f31d3cfd5d.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/haisou/haisou.go:474
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)
}
// buildShareURL 根据平台类型和分享码构建完整的分享链接
func buildShareURL(platform, shareCode string) string {
switch strings.ToLower(platform) {
case "ali":
return fmt.Sprintf("https://www.alipan.com/s/%s", shareCode)
case "baidu":
return fmt.Sprintf("https://pan.baidu.com/s/%s", shareCode)
case "quark":
return fmt.Sprintf("https://pan.quark.cn/s/%s", shareCode)
case "xunlei":
return fmt.Sprintf("https://pan.xunlei.com/s/%s", shareCode)
case "tianyi":
return fmt.Sprintf("https://cloud.189.cn/t/%s", shareCode)
default:
return ""
}View on GitHub (pinned to beaa561337)