{"record":{"id":"c0388c17e9122b1d","repo":"benbjohnson/litestream","slug":"get-connection-w","errorCode":null,"errorMessage":"get connection: %w","messagePattern":"get connection: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"db.go","lineNumber":1004,"sourceCode":"\t\tselect {\n\t\tcase <-time.After(interval):\n\t\tcase <-deadlineCtx.Done():\n\t\t\treturn fmt.Errorf(\"shutdown sync timeout after %d attempts: %w\", attempt, lastErr)\n\t\tcase <-db.Done:\n\t\t\tdb.Logger.Warn(\"shutdown sync interrupted by signal\",\n\t\t\t\t\"attempts\", attempt,\n\t\t\t\t\"duration\", time.Since(startTime))\n\t\t\treturn fmt.Errorf(\"after %d attempts: %w\", attempt, ErrShutdownInterrupted)\n\t\t}\n\t}\n}\n\n// setPersistWAL sets the PERSIST_WAL file control on the database connection.\n// This prevents SQLite from removing the WAL file when connections close.\nfunc (db *DB) setPersistWAL(ctx context.Context) error {\n\tconn, err := db.db.Conn(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"get connection: %w\", err)\n\t}\n\tdefer conn.Close()\n\n\treturn conn.Raw(func(driverConn interface{}) error {\n\t\tfc, ok := driverConn.(sqlite.FileControl)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"driver does not implement FileControl\")\n\t\t}\n\n\t\t_, err := fc.FileControlPersistWAL(\"main\", 1)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"FileControlPersistWAL: %w\", err)\n\t\t}\n\n\t\treturn nil\n\t})\n}\n","sourceCodeStart":986,"sourceCodeEnd":1022,"githubUrl":"https://github.com/benbjohnson/litestream/blob/4ed7a308f6271ebfd2b0a6e4b70b03011a37e4a3/db.go#L986-L1022","documentation":"setPersistWAL acquires a connection from the internal sql.DB pool via db.db.Conn(ctx) to issue the SQLITE_FCNTL_PERSIST_WAL file control; if acquiring the connection fails, the error is wrapped as \"get connection: %w\". This prevents SQLite from deleting the WAL file when the last connection closes, which Litestream needs for continuous monitoring. Failure typically means the pool is closed or the context is cancelled/expired.","triggerScenarios":"Calling setPersistWAL after the database connection pool has been closed, with an already-cancelled context, or when pool acquisition times out due to connection contention.","commonSituations":"Reopening a DB concurrently with its Close; shutdown racing with configuration of PERSIST_WAL; driver-level errors on the modernc.org/sqlite pool during process teardown.","solutions":["Ensure the DB is fully opened (pool alive) and no concurrent Close is running when setPersistWAL is invoked.","Pass a live, non-cancelled context with sufficient timeout to acquire a connection.","Check the wrapped error: pool closed means lifecycle bug; context deadline means increase timeout or reduce connection contention."],"exampleFix":"// before\nctx, cancel := context.WithCancel(parent)\ncancel()\nerr := db.setPersistWAL(ctx) // \"get connection: context canceled\"\n// after\nctx, cancel := context.WithTimeout(parent, 5*time.Second)\ndefer cancel()\nerr := db.setPersistWAL(ctx)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := db.setPersistWAL(ctx); err != nil {\n    if strings.Contains(err.Error(), \"get connection\") {\n        log.Printf(\"pool unavailable for PERSIST_WAL: %v\", err)\n    }\n    return err\n}","preventionTips":["Never call setPersistWAL concurrently with Close().","Use contexts with realistic timeouts for pool acquisition.","Check the wrapped error to distinguish pool-closed vs context-deadline causes."],"tags":["database","connection-pool","context","sqlite"],"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"}