From 79dbb40985b7797b783cb1ac8cb2afeebf01d20f Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 2 Mar 2026 12:37:17 +0100 Subject: [PATCH] fix(db): prevent SQLite "database is locked" errors under concurrent writes Configure SQLite connections with WAL journal mode, a 5-second busy timeout, shared cache, and a max of 1 open connection. SQLite only supports a single writer at a time, so without these settings concurrent API requests (e.g. bulk task creation) would immediately fail with "database is locked" instead of waiting and retrying. --- .gitignore | 2 ++ pkg/db/db.go | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c87048b37..c9089f1b9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ config.yml.sample !.gitea/ISSUE_TEMPLATE/config.yml docs/themes/ *.db +*.db-shm +*.db-wal Run dist/ cover.* diff --git a/pkg/db/db.go b/pkg/db/db.go index f48a6b65a..0aae21c62 100644 --- a/pkg/db/db.go +++ b/pkg/db/db.go @@ -250,7 +250,13 @@ func initSqliteEngine() (engine *xorm.Engine, err error) { } if path == "memory" { - return xorm.NewEngine("sqlite3", "file::memory:?cache=shared") + engine, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared&_busy_timeout=5000") + if err != nil { + return + } + engine.SetMaxOpenConns(1) + engine.SetMaxIdleConns(1) + return } // Log the resolved database path @@ -276,7 +282,13 @@ func initSqliteEngine() (engine *xorm.Engine, err error) { _ = os.Remove(path) // Remove the file to not prevent the db from creating another one } - return xorm.NewEngine("sqlite3", path) + engine, err = xorm.NewEngine("sqlite3", path+"?cache=shared&_busy_timeout=5000&_journal_mode=WAL") + if err != nil { + return + } + engine.SetMaxOpenConns(1) + engine.SetMaxIdleConns(1) + return } // getUserDataDir returns the platform-appropriate directory for application data