{"record":{"id":"d6ea3b4d2aed2a07","repo":"benbjohnson/litestream","slug":"after-d-attempts-w","errorCode":null,"errorMessage":"after %d attempts: %w","messagePattern":"after (.+?) attempts: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"db.go","lineNumber":934,"sourceCode":"\t\t\tcase <-deadlineCtx.Done():\n\t\t\t\tsyncCancel()\n\t\t\t}\n\t\t}()\n\t}\n\n\tvar lastErr error\n\tattempt := 0\n\tstartTime := time.Now()\n\n\tfor {\n\t\t// Check if done is already closed before attempting sync\n\t\tif db.Done != nil {\n\t\t\tselect {\n\t\t\tcase <-db.Done:\n\t\t\t\tdb.Logger.Warn(\"shutdown sync skipped, interrupted by signal\",\n\t\t\t\t\t\"attempts\", attempt,\n\t\t\t\t\t\"duration\", time.Since(startTime))\n\t\t\t\treturn fmt.Errorf(\"after %d attempts: %w\", attempt, ErrShutdownInterrupted)\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\n\t\tattempt++\n\n\t\t// Try sync\n\t\tif err := db.Replica.Sync(syncCtx); err == nil {\n\t\t\tif attempt > 1 {\n\t\t\t\tdb.Logger.Info(\"shutdown sync succeeded after retry\",\n\t\t\t\t\t\"attempts\", attempt,\n\t\t\t\t\t\"duration\", time.Since(startTime))\n\t\t\t}\n\t\t\treturn nil\n\t\t} else {\n\t\t\tlastErr = err\n\t\t}\n","sourceCodeStart":916,"sourceCodeEnd":952,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/db.go#L916-L952","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif err := db.Close(); err != nil { log.Fatal(err) }\n// after\nif err := db.Close(); err != nil {\n    if errors.Is(err, litestream.ErrShutdownInterrupted) {\n        log.Println(\"shutdown sync skipped by user signal\")\n    } else {\n        log.Fatal(err)\n    }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := db.Close(); err != nil {\n    if errors.Is(err, litestream.ErrShutdownInterrupted) {\n        log.Println(\"final sync skipped by second signal\")\n        return nil\n    }\n    return err\n}","preventionTips":["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."],"tags":["shutdown","signal","replication","retry"],"backgroundTag":"request-timeout","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}