{"record":{"id":"eed03244d6864186","repo":"AdguardTeam/AdGuardHome","slug":"starting-transaction-w","errorCode":null,"errorMessage":"starting transaction: %w","messagePattern":"starting transaction: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/aghuser/sessionstorage.go","lineNumber":152,"sourceCode":"// newBBoltLogger returns a new [*bbolt.DefaultLogger] that logs messages using\n// the given [slog.Logger].  l must not be nil.\nfunc newBBoltLogger(ctx context.Context, l *slog.Logger) (bl *bbolt.DefaultLogger) {\n\tbl = &bbolt.DefaultLogger{\n\t\tLogger: slog.NewLogLogger(l.Handler(), slog.LevelDebug),\n\t}\n\n\tif l.Enabled(ctx, slog.LevelDebug) {\n\t\tbl.EnableDebug()\n\t}\n\n\treturn bl\n}\n\n// loadSessions loads web user sessions from the bbolt database.\nfunc (ds *DefaultSessionStorage) loadSessions(ctx context.Context) (err error) {\n\ttx, err := ds.db.Begin(true)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"starting transaction: %w\", err)\n\t}\n\n\tneedRollback := true\n\tdefer func() {\n\t\tif needRollback {\n\t\t\terr = errors.WithDeferred(err, tx.Rollback())\n\t\t}\n\t}()\n\n\tbkt := tx.Bucket([]byte(bboltBucketSessions))\n\tif bkt == nil {\n\t\treturn nil\n\t}\n\n\tremoved, err := ds.processSessions(ctx, bkt)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"processing sessions: %w\", err)\n\t}","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/AdguardTeam/AdGuardHome/blob/b41aefbe51c8dde65e2c50f093996afa0502edf9/internal/aghuser/sessionstorage.go#L134-L170","documentation":"bbolt could not begin a write transaction while loading sessions at startup. The wrapped error typically indicates the database is locked by another holder, was opened read-only, or is in an unusable state.","triggerScenarios":"ds.db.Begin(true) inside loadSessions fails: the bbolt file is flocked by another process, the database was opened with a read-only option, or the file handle is already closed.","commonSituations":"Two instances of the app sharing one sessions.db; a stale lock from a crashed process that has not been released; opening the same file path twice within the process.","solutions":["Ensure a single process owns the sessions database file","Kill any stale process holding the file lock (lsof /var/lib/app/sessions.db)","If the db was opened read-only intentionally, use a storage implementation that does not write on load"],"exampleFix":"# before\ninstance1 --db /shared/sessions.db &\ninstance2 --db /shared/sessions.db &\n# after\n# run one instance, or give each its own sessions.db","handlingStrategy":"validation","validationCode":"// ensure exclusive open before constructing storage\nif _, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600); err == nil { /* first opener */ } // or use flock","typeGuard":null,"tryCatchPattern":"if err := ds.loadSessions(ctx); err != nil {\n    if errors.Is(err, bbolt.ErrDatabaseNotOpen) || isLockErr(err) { retry after resolving the lock holder }\n}","preventionTips":["Never share sessions.db across processes or instances","Open bbolt exactly once and reuse the handle","Clean up stale locks by ensuring PID 1 reaps crashed processes"],"tags":["bbolt","database","locking","transaction","startup"],"backgroundTag":"database-locked","analyzedSha":"b41aefbe51c8dde65e2c50f093996afa0502edf9","analyzedAt":"2026-08-27T04:57:55.097Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}