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
- Wait for the shutdown sync to finish (or increase the shutdown timeout) instead of sending a second signal.
- Detect errors.Is(err, litestream.ErrShutdownInterrupted) in your signal handler and accept the graceful skip rather than treating it as a storage failure.
- 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
- Send only one shutdown signal; wait for the process to exit.
- Configure a shutdown grace period longer than the worst-case final sync.
- Pre-sync via SyncAndWait before planned shutdowns.
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
- shutdown sync timeout after %d attempts: %w
- max retries exceeded reading ltx file (level=%d, min=%s, max
- sync interval must be greater than 0
- shutdown-sync-timeout must be >= 0
- shutdown sync interval must be greater than 0
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/d6ea3b4d2aed2a07.
Report an issue: GitHub.