benbjohnson/litestream · warning

after %d attempts: %w

Error message

after %d attempts: %w

What it means

During DB.Close, Litestream performs a final shutdown sync with retries (syncReplicaWithRetry). If the process receives a shutdown signal (db.Done channel closed) while retrying, it stops retrying and returns ErrShutdownInterrupted wrapped with the attempt count. This tells the caller the final sync was deliberately skipped because the user pressed Ctrl+C again.

Source

Thrown at db.go:934

			case <-deadlineCtx.Done():
				syncCancel()
			}
		}()
	}

	var lastErr error
	attempt := 0
	startTime := time.Now()

	for {
		// Check if done is already closed before attempting sync
		if db.Done != nil {
			select {
			case <-db.Done:
				db.Logger.Warn("shutdown sync skipped, interrupted by signal",
					"attempts", attempt,
					"duration", time.Since(startTime))
				return fmt.Errorf("after %d attempts: %w", attempt, ErrShutdownInterrupted)
			default:
			}
		}

		attempt++

		// Try sync
		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
		}

View on GitHub (pinned to 4ed7a308f6)

Solutions

  1. Wait for the shutdown sync to finish (or increase the shutdown timeout) instead of sending a second signal.
  2. Detect errors.Is(err, litestream.ErrShutdownInterrupted) in your signal handler and accept the graceful skip rather than treating it as a storage failure.
  3. Pre-replicate by lowering replication interval or calling SyncAndWait before shutting down.

Example fix

// before
if err := db.Close(); err != nil { log.Fatal(err) }
// after
if err := db.Close(); err != nil {
    if errors.Is(err, litestream.ErrShutdownInterrupted) {
        log.Println("shutdown sync skipped by user signal")
    } else {
        log.Fatal(err)
    }
}
Defensive patterns

Strategy: try-catch

Try / catch

if err := db.Close(); err != nil {
    if errors.Is(err, litestream.ErrShutdownInterrupted) {
        log.Println("final sync skipped by second signal")
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Calling db.Close() (or Store.Close()) while replication is failing/retrying, and a second SIGINT/SIGTERM arrives before the sync completes, closing the Done channel.

Common situations: Operator presses Ctrl+C twice to force-quit litestream while it is trying to flush the last LTX file; systemd issuing a second signal after a timeout; scripted restarts sending two signals in quick succession.

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/d6ea3b4d2aed2a07. Report an issue: GitHub.