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

  1. Inspect the wrapped lastErr with errors.Unwrap/errors.As to see the true root cause (timeout, connection refused, status code).
  2. Verify network connectivity and that the target host is reachable from the environment (curl the endpoint).
  3. Check for rate limiting/IP blocking on the upstream site and raise retryBaseDelay or maxRetries accordingly.
  4. 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

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)