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

  1. Ensure the DB is fully opened (pool alive) and no concurrent Close is running when setPersistWAL is invoked.
  2. Pass a live, non-cancelled context with sufficient timeout to acquire a connection.
  3. 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

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.