benbjohnson/litestream · error

shutdown sync timeout after %d attempts: %w

Error message

shutdown sync timeout after %d attempts: %w

What it means

syncReplicaWithRetry retries the final replication on DB.Close until a deadline context expires; when deadlineCtx.Done() fires it returns "shutdown sync timeout after N attempts" wrapping the last replication error. This means the final sync could not complete within the configured shutdown timeout despite N retries, and the last underlying error is preserved.

Source

Thrown at db.go:960

		if err := db.Replica.Sync(syncCtx); err == nil {
			if attempt > 1 {
				db.Logger.Info("shutdown sync succeeded after retry",
					"attempts", attempt,
					"duration", time.Since(startTime))
			}
			return nil
		} else {
			lastErr = err
		}

		// Check if we should stop retrying (done signal or timeout)
		select {
		case <-deadlineCtx.Done():
			db.Logger.Error("shutdown sync failed after timeout",
				"attempts", attempt,
				"duration", time.Since(startTime),
				"error", lastErr)
			return fmt.Errorf("shutdown sync timeout after %d attempts: %w", attempt, lastErr)
		case <-db.Done:
			db.Logger.Warn("shutdown sync interrupted by signal",
				"attempts", attempt,
				"duration", time.Since(startTime),
				"error", lastErr)
			return fmt.Errorf("after %d attempts: %w", attempt, ErrShutdownInterrupted)
		default:
		}

		// Log retry with hint about second signal if interruptible
		if db.Done != nil {
			db.Logger.Warn("shutdown sync failed, retrying (press Ctrl+C again to skip)",
				"attempts", attempt,
				"error", lastErr,
				"elapsed", time.Since(startTime),
				"remaining", time.Until(startTime.Add(timeout)))
		} else {
			db.Logger.Warn("shutdown sync failed, retrying",

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Inspect the wrapped lastErr (%w) for the true cause (auth, DNS, throttling) and fix the storage backend issue.
  2. Increase the shutdown sync timeout so large uploads can complete.
  3. Use errors.Is for known sentinel causes and retry the shutdown later; the data is still durable locally in the WAL/LTX files.

Example fix

// before
if err := store.Close(); err != nil { log.Fatal(err) }
// after
if err := store.Close(); err != nil {
    var terr *litestream.ShutdownTimeoutError // or match on prefix/wrapped cause
    log.Printf("shutdown sync incomplete: %v", err) // inspect %w cause: S3 403, timeout, etc.
}
Defensive patterns

Strategy: try-catch

Try / catch

if err := db.Close(); err != nil {
    if strings.Contains(err.Error(), "shutdown sync timeout") {
        log.Printf("final sync timed out, cause: %v", errors.Unwrap(errors.Unwrap(err)))
    }
    return err
}

Prevention

When it happens

Trigger: Replica client calls (e.g. S3 uploads) keep failing or hanging — network partition, throttling, wrong credentials — until the shutdown deadline elapses during db.Close().

Common situations: S3 endpoint unreachable or credentials expired at shutdown; very large pending LTX uploads on a slow link exceeding the timeout; storage provider returning 5xx/403 repeatedly.

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 benbjohnson/litestream@4ed7a308f6 (2026-09-06). Data as JSON: /api/errors/8aac789d55bc4a0d. Report an issue: GitHub.