fish2018/pansou · error

重试 次后仍然失败

Error message

重试 %d 次后仍然失败: %w

What it means

doRequestWithRetry exhausted maxRetries attempts, each returning an error, and gives up wrapping the last error with the retry count. This is the aggregation point for all transport failures in the duoduo plugin's HTTP calls, used by both searchImpl and fetchDetailLinksAndImages. The root cause (net.Error, timeout, etc.) is chained via %w in lastErr.

Solutions

  1. Unwrap and inspect lastErr (errors.Is/As for context.DeadlineExceeded, *net.OpError, x509 errors) to find the real cause.
  2. Verify reachability with curl -v against the target URL from the same host.
  3. Increase DefaultTimeout and/or maxRetries with exponential backoff between attempts.
  4. If the site is persistently unreachable, mark the plugin/source unhealthy and fall back to another source instead of hammering retries.

Example fix

// before
lastErr = err
}
return nil, fmt.Errorf("重试 %d 次后仍然失败: %w", maxRetries, lastErr)
// after
lastErr = err
time.Sleep(backoff << i) // exponential backoff between attempts
}
return nil, fmt.Errorf("重试 %d 次后仍然失败: %w", maxRetries, lastErr)
Defensive patterns

Strategy: retry

Try / catch

var lastErr error
for i := 0; i < maxRetries; i++ {
    resp, err := client.Do(req)
    if err == nil { break }
    lastErr = err
    var netErr net.Error
    if errors.As(err, &netErr) && netErr.Timeout() {
        time.Sleep(time.Duration(1<<i) * time.Second) // exponential backoff
    }
}
if lastErr != nil {
    return fmt.Errorf("after %d retries: %w", maxRetries, lastErr)
}

Prevention

When it happens

Trigger: All retry attempts of client.Do(req) fail — persistent DNS failure, connection refused/reset, TLS handshake error, or every attempt exceeding the request context deadline set by DefaultTimeout.

Common situations: The site is down or the domain has expired; a corporate firewall blocks the outbound request; the proxy configured on the http.Client is dead; the timeout is too short for a slow/blocked route so every retry also times out.

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/6dc8bbe8aba3b43d. Report an issue: GitHub.

Appendix: source

Thrown at plugin/duoduo/duoduo.go:399

			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)
}

// fetchDetailLinksAndImages 获取详情页的下载链接和图片
func (p *DuoduoAsyncPlugin) fetchDetailLinksAndImages(client *http.Client, itemID string) ([]model.Link, []string) {
	// 性能统计
	start := time.Now()
	atomic.AddInt64(&detailPageRequests, 1)
	defer func() {
		duration := time.Since(start).Nanoseconds()
		atomic.AddInt64(&totalDetailTime, duration)
	}()

	detailURL := fmt.Sprintf("https://tv.yydsys.top/index.php/vod/detail/id/%s.html", itemID)

	// 创建带超时的上下文
	ctx, cancel := context.WithTimeout(context.Background(), DetailTimeout)
	defer cancel()

View on GitHub (pinned to beaa561337)