{"record":{"id":"4f0250f51f2cd37d","repo":"benbjohnson/litestream","slug":"close-database-w","errorCode":null,"errorMessage":"close database: %w","messagePattern":"close database: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"store.go","lineNumber":415,"sourceCode":"\t\treturn fmt.Errorf(\"open database: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// DisableDB stops replication for a database.\nfunc (s *Store) DisableDB(ctx context.Context, path string) error {\n\tdb := s.FindDB(path)\n\tif db == nil {\n\t\treturn fmt.Errorf(\"database not found: %s\", path)\n\t}\n\n\tif !db.IsOpen() {\n\t\treturn fmt.Errorf(\"database already disabled: %s\", path)\n\t}\n\n\tif err := db.Close(ctx); err != nil {\n\t\treturn fmt.Errorf(\"close database: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// SyncDBResult holds the result of a sync operation.\ntype SyncDBResult struct {\n\tTXID           uint64\n\tReplicatedTXID uint64\n\tChanged        bool\n}\n\n// SyncDB forces an immediate sync for a database. If wait is true, blocks\n// until both WAL-to-LTX and LTX-to-remote sync complete. If wait is false,\n// only performs the WAL-to-LTX sync and lets the replica monitor handle upload.\n// Lock waits are context-aware: the timeout is honored while waiting for\n// the database sync executor and the replica sync lock.\nfunc (s *Store) SyncDB(ctx context.Context, path string, wait bool) (SyncDBResult, error) {","sourceCodeStart":397,"sourceCodeEnd":433,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/store.go#L397-L433","documentation":"Wraps db.Close failure in Store.DisableDB. Fires when disabling a database: the DB was open and eligible for disable, but closing its resources (WAL monitoring, open handles) failed, so the disable operation is reported as failed instead of silently leaving it half-closed.","triggerScenarios":"Store.DisableDB(ctx, path) where db.Close fails — sync executor or replica sync holding the lock, ctx cancelled mid-close, or an internal close error on the SQLite handle.","commonSituations":"Disabling a database during active replication traffic; a context deadline expiring while Close waits for the sync executor; shutdown racing a disable command.","solutions":["Inspect the wrapped cause to identify whether it was cancellation or a replica/executor error","Retry DisableDB with a fresh, longer-timeout context after in-flight syncs drain","Check replica/storage connectivity if the cause points at replica cleanup","Ensure no other goroutine is calling SyncDB concurrently with the disable"],"exampleFix":"// before\nctx, cancel := context.WithTimeout(ctx, 1*time.Millisecond) // too short\nif err := store.DisableDB(ctx, path); err != nil { ... }\n// after\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\ndefer cancel()\nif err := store.DisableDB(ctx, path); err != nil { ... }","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := store.DisableDB(ctx, path); err != nil {\n    var cause error\n    errors.As(err, &cause)\n    if errors.Is(cause, context.DeadlineExceeded) || errors.Is(cause, context.Canceled) {\n        ctx2, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n        defer cancel()\n        return store.DisableDB(ctx2, path)\n    }\n    return err\n}","preventionTips":["Give DisableDB a generous timeout — it waits for syncs to drain","Avoid issuing SyncDB concurrently with DisableDB","Log the full error chain to distinguish cancellation from replica failures"],"tags":["database","close","cancellation"],"backgroundTag":"database-query-failed","analyzedSha":"4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3","analyzedAt":"2026-09-06T18:29:25.564Z","contentChangedAt":"2026-09-06T18:29:25.564Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}