Skip to content

Commit

Permalink
sqlite: set connection attributes on open
Browse files Browse the repository at this point in the history
The symptoms in #17859 indicate that setting the PRAGMAs in individual
EXECs outside of a transaction can lead to concurrency issues and
failures when the DB is locked.  Hence set all PRAGMAs when opening
the connection.  Move them into individual constants to improve
documentation and readability.

Further make transactions exclusive as #17859 also mentions an error
that the DB is locked during a transaction.

[NO NEW TESTS NEEDED] - existing tests cover the code.

Fixes: #17859
Signed-off-by: Valentin Rothberg <[email protected]>

<MH: Cherry-picked on top of my branch>

Signed-off-by: Matthew Heon <[email protected]>
  • Loading branch information
vrothberg authored and mheon committed Mar 21, 2023
1 parent 9f0e0e8 commit 0fbc325
Showing 1 changed file with 25 additions and 15 deletions.
40 changes: 25 additions & 15 deletions libpod/sqlite_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,30 @@ type SQLiteState struct {
runtime *Runtime
}

const (
// Deal with timezone automatically.
sqliteOptionLocation = "_loc=auto"
// Set the journal mode (https://www.sqlite.org/pragma.html#pragma_journal_mode).
sqliteOptionJournal = "&_journal=WAL"
// Force WAL mode to fsync after each transaction (https://www.sqlite.org/pragma.html#pragma_synchronous).
sqliteOptionSynchronous = "&_sync=FULL"
// Allow foreign keys (https://www.sqlite.org/pragma.html#pragma_foreign_keys).
sqliteOptionForeignKeys = "&_foreign_keys=1"
// Enable cache sharing for threads within a process
sqliteOptionSharedCache = "&cache=shared"
// Make sure that transactions happen exclusively.
sqliteOptionTXLock = "&_txlock=exclusive"

// Assembled sqlite options used when opening the database.
sqliteOptions = "db.sql?" +
sqliteOptionLocation +
sqliteOptionJournal +
sqliteOptionSynchronous +
sqliteOptionForeignKeys +
sqliteOptionSharedCache +
sqliteOptionTXLock
)

// NewSqliteState creates a new SQLite-backed state database.
func NewSqliteState(runtime *Runtime) (_ State, defErr error) {
state := new(SQLiteState)
Expand All @@ -45,7 +69,7 @@ func NewSqliteState(runtime *Runtime) (_ State, defErr error) {
return nil, fmt.Errorf("creating root directory: %w", err)
}

conn, err := sql.Open("sqlite3", filepath.Join(basePath, "db.sql?_loc=auto&cache=shared"))
conn, err := sql.Open("sqlite3", filepath.Join(basePath, sqliteOptions))
if err != nil {
return nil, fmt.Errorf("initializing sqlite database: %w", err)
}
Expand All @@ -66,20 +90,6 @@ func NewSqliteState(runtime *Runtime) (_ State, defErr error) {
return nil, fmt.Errorf("cannot connect to database: %w", err)
}

// Enable foreign keys constraints, which we use extensively in our
// tables.
if _, err := state.conn.Exec("PRAGMA foreign_keys = ON;"); err != nil {
return nil, fmt.Errorf("enabling foreign key support in database: %w", err)
}
// Enable WAL mode for performance - https://www.sqlite.org/wal.html
if _, err := state.conn.Exec("PRAGMA journal_mode = WAL;"); err != nil {
return nil, fmt.Errorf("switching journal to WAL mode: %w", err)
}
// Force WAL mode to fsync after every transaction, for reboot safety.
if _, err := state.conn.Exec("PRAGMA synchronous = FULL;"); err != nil {
return nil, fmt.Errorf("setting full fsync mode in db: %w", err)
}

// Migrate schema (if necessary)
if err := state.migrateSchemaIfNecessary(); err != nil {
return nil, err
Expand Down

0 comments on commit 0fbc325

Please sign in to comment.