benbjohnson/litestream · error
get connection: %w
Error message
get connection: %w
What it means
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.
Source
Thrown at db.go:1004
select {
case <-time.After(interval):
case <-deadlineCtx.Done():
return fmt.Errorf("shutdown sync timeout after %d attempts: %w", attempt, lastErr)
case <-db.Done:
db.Logger.Warn("shutdown sync interrupted by signal",
"attempts", attempt,
"duration", time.Since(startTime))
return fmt.Errorf("after %d attempts: %w", attempt, ErrShutdownInterrupted)
}
}
}
// setPersistWAL sets the PERSIST_WAL file control on the database connection.
// This prevents SQLite from removing the WAL file when connections close.
func (db *DB) setPersistWAL(ctx context.Context) error {
conn, err := db.db.Conn(ctx)
if err != nil {
return fmt.Errorf("get connection: %w", err)
}
defer conn.Close()
return conn.Raw(func(driverConn interface{}) error {
fc, ok := driverConn.(sqlite.FileControl)
if !ok {
return fmt.Errorf("driver does not implement FileControl")
}
_, err := fc.FileControlPersistWAL("main", 1)
if err != nil {
return fmt.Errorf("FileControlPersistWAL: %w", err)
}
return nil
})
}
View on GitHub (pinned to 4ed7a308f6)
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.
Example fix
// before ctx, cancel := context.WithCancel(parent) cancel() err := db.setPersistWAL(ctx) // "get connection: context canceled" // after ctx, cancel := context.WithTimeout(parent, 5*time.Second) defer cancel() err := db.setPersistWAL(ctx)
Defensive patterns
Strategy: try-catch
Try / catch
if err := db.setPersistWAL(ctx); err != nil {
if strings.Contains(err.Error(), "get connection") {
log.Printf("pool unavailable for PERSIST_WAL: %v", err)
}
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.
Related errors
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/c0388c17e9122b1d.
Report an issue: GitHub.