{"record":{"id":"b78644bdd9596c38","repo":"benbjohnson/litestream","slug":"close-db-w","errorCode":null,"errorMessage":"close db: %w","messagePattern":"close db: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"store.go","lineNumber":372,"sourceCode":"\tvar db *DB\n\tfor i, existing := range s.dbs {\n\t\tif existing.Path() == path {\n\t\t\tidx = i\n\t\t\tdb = existing\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif db == nil {\n\t\ts.mu.Unlock()\n\t\treturn nil\n\t}\n\n\ts.dbs = slices.Delete(s.dbs, idx, idx+1)\n\ts.mu.Unlock()\n\n\tif err := db.Close(ctx); err != nil {\n\t\treturn fmt.Errorf(\"close db: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// EnableDB starts replication for a registered database.\n// The context is checked for cancellation before opening.\n// Note: db.Open() itself does not support cancellation.\nfunc (s *Store) EnableDB(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 enabled: %s\", path)\n\t}\n","sourceCodeStart":354,"sourceCodeEnd":390,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/store.go#L354-L390","documentation":"UnregisterDB removes a database from the Store's registry and then closes it. This error wraps any failure returned by db.Close(ctx) during that teardown, so the DB may be unregistered but not fully shut down (WAL monitor, replicas, executors). Because it's a wrapped error, the underlying cause (e.g. replica close failure, context cancellation) is in the %w chain.","triggerScenarios":"Calling Store.UnregisterDB(ctx, path) for a registered DB whose Close fails — e.g. db.Open() active, replica sync in flight, or ctx cancelled while close waits for locks.","commonSituations":"IPC-driven unregister (handleUnregister) or config reload (removeDatabase/removeDatabasesUnder) while the database is mid-sync; shutting down litestream with an open DB; cancelling the context passed to UnregisterDB during a slow close.","solutions":["Inspect the wrapped cause with errors.Unwrap / %v to see why Close failed","Retry UnregisterDB once the in-flight sync completes; Close is retriable after the transient condition clears","Pass a context with an adequate timeout instead of one already near cancellation","Check replica/storage connectivity if the wrapped error points at replica cleanup"],"exampleFix":"// before\nif err := db.Close(ctx); err != nil {\n    return fmt.Errorf(\"close db: %w\", err)\n}\n// after\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\ndefer cancel()\nif err := db.Close(ctx); err != nil {\n    return fmt.Errorf(\"close db: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"if db := store.FindDB(path); db == nil {\n    return fmt.Errorf(\"db not registered: %s\", path)\n}","typeGuard":null,"tryCatchPattern":"if err := store.UnregisterDB(ctx, path); err != nil {\n    var ctxErr error\n    if errors.As(err, &ctxErr) && errors.Is(ctxErr, context.Canceled) {\n        // retry with fresh context\n    }\n    return fmt.Errorf(\"unregister %s: %w\", path, err)\n}","preventionTips":["Always pass a live context with sufficient timeout to UnregisterDB","Avoid unregistering while syncs are in flight; drain first","Log the full wrapped error chain, not just the top-level message"],"tags":["database","close","lifecycle"],"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"}