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

  1. Check why the context was cancelled — log errors.Is(err, context.Canceled) vs context.DeadlineExceeded.
  2. Increase the timeout or use a context detached from request scope for background epoch refresh.
  3. Ensure Refresh is called with a context that outlives the refresh loop (e.g. service-lifetime context).
  4. 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

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


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)