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

  1. Inspect the wrapped lastErr to identify the root cause (timeout vs refused vs reset)
  2. Increase maxRetries and add exponential backoff with jitter between attempts
  3. Confirm upstream availability (curl/status page) and configure a proxy if the host is blocked in your network
  4. Verify http.Client settings (Timeout, Transport, proxy env vars)
  5. 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

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)