{"record":{"id":"b87c686d0fa23964","repo":"AlistGo/alist","slug":"after-d-attempts-last-error-s","errorCode":null,"errorMessage":"after %d attempts, last error: %s","messagePattern":"after (.+?) attempts, last error: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/utils/io.go","lineNumber":152,"sourceCode":"\t\treturn closer.Close()\n\t}\n\treturn nil\n}\n\nfunc Retry(attempts int, sleep time.Duration, f func() error) (err error) {\n\tfor i := 0; i < attempts; i++ {\n\t\t//fmt.Println(\"This is attempt number\", i)\n\t\tif i > 0 {\n\t\t\tlog.Println(\"retrying after error:\", err)\n\t\t\ttime.Sleep(sleep)\n\t\t\tsleep *= 2\n\t\t}\n\t\terr = f()\n\t\tif err == nil {\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn fmt.Errorf(\"after %d attempts, last error: %s\", attempts, err)\n}\n\ntype ClosersIF interface {\n\tio.Closer\n\tAdd(closer io.Closer)\n\tAddClosers(closers Closers)\n\tGetClosers() Closers\n}\n\ntype Closers struct {\n\tclosers []io.Closer\n}\n\nfunc (c *Closers) GetClosers() Closers {\n\treturn *c\n}\n\nvar _ ClosersIF = (*Closers)(nil)","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/pkg/utils/io.go#L134-L170","documentation":"pkg/utils.Retry(attempts, sleep, f) runs f up to `attempts` times with exponential backoff (sleep doubling each retry). If every attempt errors, it returns 'after %d attempts, last error: %s' containing only the final error's text — the underlying error is not wrapped, just formatted.","triggerScenarios":"Any operation wrapped in utils.Retry that keeps failing for all attempts: unreachable HTTP endpoints, failing conversions, or persistently erroring driver calls.","commonSituations":"Treating this as the root cause when it is only a wrapper — the real cause is inside 'last error:'. Search/log inspection for the last error string is needed. Also: fixed attempts give up too early on slow-recovering dependencies.","solutions":["Read the text after 'last error:' — that is the actual failure to fix","Increase attempts / initial sleep for flaky dependencies (note: no context cancellation support)","Fix the underlying error (network, credentials, etc.) rather than tuning retries","If you need errors.Is/As on the cause, wrap it yourself since Retry only formats it as a string"],"exampleFix":"// before\nerr := utils.Retry(3, time.Second, fetch)\nif err != nil { /* err is opaque text */ }\n\n// after (capture the cause)\nvar lastErr error\nerr := utils.Retry(3, time.Second, func() error {\n    lastErr = fetch()\n    return lastErr\n})\nif err != nil { /* inspect lastErr typed value */ }","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"var lastErr error\nerr := utils.Retry(3, time.Second, func() error {\n    lastErr = op()\n    return lastErr\n})\nif err != nil {\n    // err is formatted text; use lastErr for errors.Is/As and typed handling\n    if errors.Is(lastErr, io.EOF) { ... }\n}","preventionTips":["Capture the last operation error in a closure since Retry only stringifies it","Size attempts/sleep to the dependency's recovery time","Fix root causes instead of raising attempt counts"],"tags":["go","retry","backoff","utilities"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}