{"record":{"id":"c200fd61857aace8","repo":"fish2018/pansou","slug":"d-w-c200fd","errorCode":null,"errorMessage":"重试 %d 次后失败: %w","messagePattern":"重试 (.+?) 次后失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/jupansou/jupansou.go","lineNumber":340,"sourceCode":"func doJuPansouRequestWithRetry(req *http.Request, client *http.Client) (*http.Response, error) {\n\tvar lastErr error\n\tfor attempt := 0; attempt < jupansouMaxRetries; attempt++ {\n\t\tresp, err := client.Do(req.Clone(req.Context()))\n\t\tif err == nil && resp.StatusCode == http.StatusOK {\n\t\t\treturn resp, nil\n\t\t}\n\t\tif resp != nil {\n\t\t\tresp.Body.Close()\n\t\t}\n\t\tlastErr = err\n\t\tif lastErr == nil {\n\t\t\tlastErr = fmt.Errorf(\"HTTP 状态码 %d\", resp.StatusCode)\n\t\t}\n\t\tif attempt < jupansouMaxRetries-1 {\n\t\t\ttime.Sleep(200 * time.Millisecond * time.Duration(1<<attempt))\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"重试 %d 次后失败: %w\", jupansouMaxRetries, lastErr)\n}\n","sourceCodeStart":322,"sourceCodeEnd":342,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/jupansou/jupansou.go#L322-L342","documentation":"doJuPansouRequestWithRetry returns this after jupansouMaxRetries attempts all failed, wrapping lastErr (either the last transport error or the last non-2xx status error). It is the terminal failure of the HTTP fetch step for the JuPansou search and propagates to searchImpl's \"请求失败\" wrapper.","triggerScenarios":"Every retry attempt in the loop failed — persistent connection errors, timeouts, or repeated non-2xx status codes — with exponential backoff (200ms<<attempt) between attempts, until the loop ends.","commonSituations":"Sustained upstream outage; IP blocked or rate-limited for the entire retry window; DNS failures in the deployment environment; timeout too short for every attempt.","solutions":["Unwrap the chain to see the last attempt's cause (errors.Unwrap twice: retry wrapper → status/transport error).","Test the endpoint manually with curl from the same host.","Increase jupansouMaxRetries or the backoff base if the outage is brief.","Apply exponential backoff with jitter and circuit-breaking to avoid hammering a downed upstream.","Check for IP-level bans; rotate egress IP or use a proxy."],"exampleFix":"// before: fixed retry count, no jitter\ntime.Sleep(200 * time.Millisecond * time.Duration(1<<attempt))\n// after: add jitter to avoid thundering-hered retries\ntime.Sleep(time.Duration(float64(200*time.Millisecond) * (1 << attempt) * (0.5 + rand.Float64())))","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"results, err := p.searchImpl(client, keyword)\nif err != nil && strings.Contains(err.Error(), \"重试\") {\n    log.Printf(\"jupansou exhausted retries: %v\", err)\n    results = cache.Get(keyword) // stale fallback\n}","preventionTips":["Use exponential backoff with jitter and an upper bound on total wait.","Cache last-good results to survive full retry exhaustion.","Circuit-break a source after repeated exhaustion and re-enable it after a cooldown.","Monitor retry-exhaustion rates per source to detect upstream degradation early."],"tags":["network","http","retry","upstream"],"backgroundTag":"api-request-failed","analyzedSha":"beaa56133755a548ebc51b090b3816e2ae044aa6","analyzedAt":"2026-09-07T00:31:18.025Z","contentChangedAt":"2026-09-07T00:31:18.025Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}