fish2018/pansou · error
重试 次后失败
Error message
重试 %d 次后失败: %w
What it means
alupan's doRequestWithRetry attempts an HTTP request up to maxRetries times with exponential backoff (retryBaseDelay=200ms doubling per attempt). If every attempt fails, it returns the final wrapped error '重试 %d 次后失败: %w' so callers (searchImpl, fetchDetailLinks) know the retries were exhausted, preserving the underlying cause via %w.
Solutions
- Inspect the wrapped lastErr with errors.Unwrap/errors.As to see the true root cause (timeout, connection refused, status code).
- Verify network connectivity and that the target host is reachable from the environment (curl the endpoint).
- Check for rate limiting/IP blocking on the upstream site and raise retryBaseDelay or maxRetries accordingly.
- Add a timeout-aware http.Client and, if the site requires it, valid cookies/headers set before the request.
Example fix
// before
results, err := p.searchImpl(keyword)
if err != nil { return nil, err }
// after
results, err := p.searchImpl(keyword)
if err != nil {
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
return nil, fmt.Errorf("upstream timed out: %w", err)
}
return nil, err // surface wrapped cause for diagnosis
} Defensive patterns
Strategy: retry
Validate before calling
func isRetryable(err error) bool { var ne net.Error; return errors.As(err, &ne) || errors.Is(err, context.DeadlineExceeded) } Try / catch
res, err := plugin.Search(keyword)
if err != nil {
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
// schedule retry with longer deadline
} else {
log.Printf("search failed permanently: %v", err)
}
} Prevention
- Health-check the upstream host before batch searches
- Tune retryBaseDelay/maxRetries to the site's actual latency
- Monitor for 429/403 rates and back off proactively
- Always inspect the wrapped cause with errors.As/Is
When it happens
Trigger: All maxRetries attempts of the HTTP request inside doRequestWithRetry fail — e.g. the alupan search or detail endpoint is unreachable, times out, or the transport keeps returning non-recoverable errors; called from searchImpl (alupan.go:131) or fetchDetailLinks (alupan.go:261).
Common situations: Target site is down or blocking (403/429), DNS failure, no network access, TLS errors, or a proxy misconfiguration making every retry fail within the backoff window.
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/432cc55eb7d823d0.
Report an issue: GitHub.
Appendix: source
Thrown at plugin/alupan/alupan.go:401
func (p *AlupanPlugin) doRequestWithRetry(req *http.Request, client *http.Client, maxRetries int) (*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)