Prevent SQLite busy errors

This commit is contained in:
benya
2026-06-04 16:45:36 +03:00
parent 959a70b6d2
commit 6fc805491e
2 changed files with 53 additions and 0 deletions
+14
View File
@@ -54,6 +54,20 @@ func OpenSQLite(ctx context.Context, path string) (*Store, error) {
return nil, err
}
// SQLite permits one writer at a time. Keep access serialized inside this
// process and wait briefly for locks held during startup or maintenance.
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)
for _, stmt := range []string{
`PRAGMA busy_timeout = 5000`,
`PRAGMA journal_mode = WAL`,
} {
if _, err := db.ExecContext(ctx, stmt); err != nil {
_ = db.Close()
return nil, err
}
}
s := &Store{db: db}
if err := s.Migrate(ctx); err != nil {
_ = db.Close()
+39
View File
@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"path/filepath"
"sync"
"testing"
_ "modernc.org/sqlite"
@@ -63,3 +64,41 @@ VALUES ('rs_legacy', 'dev_legacy');
t.Fatalf("got target %q, want ssh", sessions[0].Target)
}
}
func TestSQLiteConcurrentAccessDoesNotReturnBusy(t *testing.T) {
ctx := context.Background()
st, err := OpenSQLite(ctx, filepath.Join(t.TempDir(), "concurrent.db"))
if err != nil {
t.Fatal(err)
}
defer st.Close()
enrolled, err := st.EnrollDevice(ctx, "router", "OpenWrt")
if err != nil {
t.Fatal(err)
}
var wg sync.WaitGroup
errs := make(chan error, 80)
for range 40 {
wg.Add(2)
go func() {
defer wg.Done()
_, _, err := st.ListRemoteSessions(ctx, enrolled.DeviceID, RemoteSessionListOptions{})
errs <- err
}()
go func() {
defer wg.Done()
_, err := st.SaveHeartbeat(ctx, enrolled.DeviceID, []byte(`{"hostname":"router"}`), []byte(`{}`))
errs <- err
}()
}
wg.Wait()
close(errs)
for err := range errs {
if err != nil {
t.Fatal(err)
}
}
}