mirror of
https://github.com/mudler/LocalAI.git
synced 2026-06-25 17:12:10 -04:00
* fix(auth): make advisory locks dialect-aware and harden SQLite DSN Fixes #10506. Two failures hit deployments that use the default SQLite auth database: 1. advisorylock executed PostgreSQL-only SQL (pg_advisory_lock / pg_try_advisory_lock) unconditionally. On a SQLite auth DB the job store, agent store and node registry migrations failed with "no such function: pg_advisory_lock". WithLockCtx/TryWithLockCtx now branch on the gorm dialect: PostgreSQL keeps the cross-process advisory lock, every other dialect uses a context-aware, per-key in-process lock (a SQLite auth DB is effectively single-process, so serializing within the process is sufficient). 2. The SQLite auth DSN set no busy timeout, so transient SQLITE_BUSY over network-backed storage (SMB/CIFS/NFS, e.g. Azure Files) failed the auth migration immediately with "database is locked". The DSN now sets _busy_timeout=5000 and _txlock=immediate (caller-supplied values are preserved). WAL is intentionally not enabled since its shared-memory mmap does not work over network filesystems. Docs note that PostgreSQL should be used when the data directory lives on shared storage. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:claude-opus-4-8 [Claude Code] * test(jobs): regression test for #10506 SQLite job store migration Exercises the exact caller chain that failed in the issue: auth.InitDB(sqlite) -> jobs.NewJobStore -> advisorylock.WithLockCtx -> AutoMigrate. Before the dialect-aware advisory lock fix this failed with "no such function: pg_advisory_lock"; the test now asserts it migrates cleanly on a SQLite auth DB. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude:claude-opus-4-8 [Claude Code] --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
54 lines
1.5 KiB
Go
54 lines
1.5 KiB
Go
//go:build auth
|
|
|
|
package auth
|
|
|
|
import (
|
|
"net/url"
|
|
"strings"
|
|
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func openSQLiteDialector(path string) (gorm.Dialector, error) {
|
|
return sqlite.Open(buildSQLiteDSN(path)), nil
|
|
}
|
|
|
|
// buildSQLiteDSN augments a SQLite file path with connection pragmas that make
|
|
// the auth DB resilient on slow or contended storage.
|
|
//
|
|
// - _busy_timeout=5000 makes SQLite retry for up to 5s on SQLITE_BUSY instead
|
|
// of failing immediately. Network-backed storage (SMB/CIFS/NFS, e.g. Azure
|
|
// Files) is prone to transient lock contention during migration (see #10506).
|
|
// - _txlock=immediate takes the write lock at BEGIN, avoiding deadlocks when a
|
|
// read transaction later upgrades to a write during AutoMigrate.
|
|
//
|
|
// We deliberately do NOT set WAL journal mode: WAL relies on a shared-memory
|
|
// mmap that does not work over SMB/NFS, which is exactly the failing case here.
|
|
//
|
|
// Caller-supplied values for either pragma are preserved.
|
|
func buildSQLiteDSN(path string) string {
|
|
base := path
|
|
rawQuery := ""
|
|
if i := strings.IndexByte(path, '?'); i >= 0 {
|
|
base = path[:i]
|
|
rawQuery = path[i+1:]
|
|
}
|
|
|
|
values, err := url.ParseQuery(rawQuery)
|
|
if err != nil {
|
|
// An unparseable query string means a hand-crafted DSN we should not
|
|
// risk corrupting; leave it untouched.
|
|
return path
|
|
}
|
|
|
|
if values.Get("_busy_timeout") == "" {
|
|
values.Set("_busy_timeout", "5000")
|
|
}
|
|
if values.Get("_txlock") == "" {
|
|
values.Set("_txlock", "immediate")
|
|
}
|
|
|
|
return base + "?" + values.Encode()
|
|
}
|