on main help sign in
fix(db): prevent SQLITE_NOTADB corruption under concurrent sub-agents (#2690)
4010841Greg Slepak
internal/db/connect.go+7
@@ -50,6 +50,13 @@ func Connect(ctx context.Context, dataDir string) (*sql.DB, error) {
50 return nil, err
50 return nil, err
51 }
51 }
52
52
53+ // Serialize all access through a single connection. SQLite serializes
54+ // writes at the file level anyway, and allowing multiple pool
55+ // connections to interleave writes/checkpoints (especially under
56+ // concurrent sub-agents) has caused WAL/header desync resulting in
57+ // SQLITE_NOTADB (26) on the next open.
58+ db.SetMaxOpenConns(1)
59+
53 if err = db.PingContext(ctx); err != nil {
60 if err = db.PingContext(ctx); err != nil {
54 db.Close()
61 db.Close()
55 return nil, fmt.Errorf("failed to connect to database: %w", err)
62 return nil, fmt.Errorf("failed to connect to database: %w", err)
internal/db/connect_modernc.go+3
@@ -17,6 +17,9 @@ func openDB(dbPath string) (*sql.DB, error) {
17 for name, value := range pragmas {
17 for name, value := range pragmas {
18 params.Add("_pragma", fmt.Sprintf("%s(%s)", name, value))
18 params.Add("_pragma", fmt.Sprintf("%s(%s)", name, value))
19 }
19 }
20+ // Use BEGIN IMMEDIATE so writers acquire the reserved lock up front,
21+ // preventing deferred-to-writer upgrade deadlocks.
22+ params.Set("_txlock", "immediate")
20
23
21 dsn := fmt.Sprintf("file:%s?%s", dbPath, params.Encode())
24 dsn := fmt.Sprintf("file:%s?%s", dbPath, params.Encode())
22 db, err := sql.Open("sqlite", dsn)
25 db, err := sql.Open("sqlite", dsn)
internal/db/connect_ncruces.go+5−1
@@ -11,7 +11,11 @@ import (
11)
11)
12
12
13func openDB(dbPath string) (*sql.DB, error) {
13func openDB(dbPath string) (*sql.DB, error) {
14+ // Use BEGIN IMMEDIATE so writers acquire the reserved lock up front,
15+ // preventing deferred-to-writer upgrade deadlocks. The "file:" prefix
16+ // is required for the ncruces driver to parse query parameters.
17+ dsn := fmt.Sprintf("file:%s?_txlock=immediate", dbPath)
14− db, err := driver.Open(dbPath, func(c *sqlite3.Conn) error {
18+ db, err := driver.Open(dsn, func(c *sqlite3.Conn) error {
15 // Set pragmas for better performance via _pragma query params.
19 // Set pragmas for better performance via _pragma query params.
16 // Format: PRAGMA name = value;
20 // Format: PRAGMA name = value;
17 for name, value := range pragmas {
21 for name, value := range pragmas {