{"record":{"id":"ea1d45619162a73c","repo":"kopia/kopia","slug":"refreshlocked","errorCode":null,"errorMessage":"refreshLocked","messagePattern":"refreshLocked","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/epoch/epoch_manager.go","lineNumber":468,"sourceCode":"\t}\n\n\t// compaction set was written sufficiently long ago to be reliably discovered by all\n\t// other clients - we can delete uncompacted blobs for this epoch.\n\treturn blob.MaxTimestamp(replacementSet).Before(maxReplacementTime)\n}\n\nfunc (e *Manager) getParameters(ctx context.Context) (*Parameters, error) {\n\temp, err := e.paramProvider.GetParameters(ctx)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"epoch manager parameters\")\n\t}\n\n\treturn emp, nil\n}\n\nfunc (e *Manager) refreshLocked(ctx context.Context) error {\n\tif ctx.Err() != nil {\n\t\treturn errors.Wrap(ctx.Err(), \"refreshLocked\")\n\t}\n\n\tp, err := e.getParameters(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnextDelayTime := initialRefreshAttemptSleep\n\n\tif !p.Enabled {\n\t\treturn errors.New(\"epoch manager not enabled\")\n\t}\n\n\tfor err := e.refreshAttemptLocked(ctx); err != nil; err = e.refreshAttemptLocked(ctx) {\n\t\tif ctx.Err() != nil {\n\t\t\treturn errors.Wrap(ctx.Err(), \"refreshAttemptLocked\")\n\t\t}\n","sourceCodeStart":450,"sourceCodeEnd":486,"githubUrl":"https://github.com/kopia/kopia/blob/82495e54b584c1ef6073c9e1be048f57f8aef078/internal/epoch/epoch_manager.go#L450-L486","documentation":"refreshLocked wraps the context error (ctx.Err()) when the context is already cancelled or its deadline has expired before a refresh attempt begins. It signals that the epoch manager's Refresh/committedState path cannot proceed because the caller's context is no longer valid — not an internal fault.","triggerScenarios":"Calling Refresh (directly or via committedState) with a context that was cancelled (parent shutdown, explicit cancel(), or deadline exceeded) before or during the refresh loop.","commonSituations":"HTTP request handler times out mid-refresh; service shutdown cancels background refresh goroutines; caller passes context.Background-less short timeouts into Refresh.","solutions":["Check why the context was cancelled — log errors.Is(err, context.Canceled) vs context.DeadlineExceeded.","Increase the timeout or use a context detached from request scope for background epoch refresh.","Ensure Refresh is called with a context that outlives the refresh loop (e.g. service-lifetime context).","Fix upstream cancellation: avoid cancelling the parent context while committedState work is in flight."],"exampleFix":"// before\nctx, cancel := context.WithTimeout(reqCtx, 100*time.Millisecond)\nerr := mgr.Refresh(ctx)\n\n// after: use service-lifetime context for background refresh\ncancel := context.AfterFunc(done, func() { refreshCtxCancel() })\n_ = cancel\nerr := mgr.Refresh(refreshCtx)","handlingStrategy":"try-catch","validationCode":"if ctx.Err() != nil { return fmt.Errorf(\"cannot refresh: %w\", ctx.Err()) }","typeGuard":"func isContextErr(err error) bool {\n    return errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)\n}","tryCatchPattern":"if err := mgr.Refresh(ctx); err != nil {\n    if isContextErr(err) {\n        // caller cancelled or deadline hit; reschedule instead of treating as storage fault\n        return nil\n    }\n    return err\n}","preventionTips":["Use a service-lifetime context for background epoch refresh, not request-scoped contexts.","Set refresh timeouts comfortably above worst-case refresh duration.","Distinguish context errors from storage errors before alerting.","Gracefully drain in-flight Refresh calls on shutdown before cancelling."],"tags":["context-cancelled","refresh","epoch-manager"],"backgroundTag":"request-timeout","analyzedSha":"82495e54b584c1ef6073c9e1be048f57f8aef078","analyzedAt":"2026-09-07T20:35:21.689Z","contentChangedAt":"2026-09-07T20:35:21.689Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}