kopia/kopia · error
refreshLocked
Error message
refreshLocked
What it means
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.
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.
Example fix
// before
ctx, cancel := context.WithTimeout(reqCtx, 100*time.Millisecond)
err := mgr.Refresh(ctx)
// after: use service-lifetime context for background refresh
cancel := context.AfterFunc(done, func() { refreshCtxCancel() })
_ = cancel
err := mgr.Refresh(refreshCtx) Defensive patterns
Strategy: try-catch
Validate before calling
if ctx.Err() != nil { return fmt.Errorf("cannot refresh: %w", ctx.Err()) } Type guard
func isContextErr(err error) bool {
return errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)
} Try / catch
if err := mgr.Refresh(ctx); err != nil {
if isContextErr(err) {
// caller cancelled or deadline hit; reschedule instead of treating as storage fault
return nil
}
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: HTTP request handler times out mid-refresh; service shutdown cancels background refresh goroutines; caller passes context.Background-less short timeouts into Refresh.
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
- refreshAttemptLocked
- epoch manager
- epoch manager parameters
- error deleting partially written shard
- error getting committed state
AI-assisted analysis of kopia/kopia@82495e54b5 (2026-09-07).
Data as JSON: /api/errors/ea1d45619162a73c.
Report an issue: GitHub.
Appendix: source
Thrown at internal/epoch/epoch_manager.go:468
}
// compaction set was written sufficiently long ago to be reliably discovered by all
// other clients - we can delete uncompacted blobs for this epoch.
return blob.MaxTimestamp(replacementSet).Before(maxReplacementTime)
}
func (e *Manager) getParameters(ctx context.Context) (*Parameters, error) {
emp, err := e.paramProvider.GetParameters(ctx)
if err != nil {
return nil, errors.Wrap(err, "epoch manager parameters")
}
return emp, nil
}
func (e *Manager) refreshLocked(ctx context.Context) error {
if ctx.Err() != nil {
return errors.Wrap(ctx.Err(), "refreshLocked")
}
p, err := e.getParameters(ctx)
if err != nil {
return err
}
nextDelayTime := initialRefreshAttemptSleep
if !p.Enabled {
return errors.New("epoch manager not enabled")
}
for err := e.refreshAttemptLocked(ctx); err != nil; err = e.refreshAttemptLocked(ctx) {
if ctx.Err() != nil {
return errors.Wrap(ctx.Err(), "refreshAttemptLocked")
}
View on GitHub (pinned to 82495e54b5)