Fix database locked errors (#3153)

* Make read-only operations use WithReadTxn
* Allow one database write thread
* Add unit test for concurrent transactions
* Perform some actions after commit to release txn
* Suppress some errors from cancelled context
This commit is contained in:
WithoutPants
2022-11-21 06:49:10 +11:00
committed by GitHub
parent 420c6fa9d7
commit f39fa416a9
54 changed files with 626 additions and 311 deletions

View File

@@ -8,7 +8,6 @@ import (
"fmt"
"os"
"path/filepath"
"sync"
"time"
"github.com/fvbommel/sortorder"
@@ -73,7 +72,7 @@ type Database struct {
schemaVersion uint
writeMu sync.Mutex
lockChan chan struct{}
}
func NewDatabase() *Database {
@@ -87,6 +86,7 @@ func NewDatabase() *Database {
Image: NewImageStore(fileStore),
Gallery: NewGalleryStore(fileStore, folderStore),
Performer: NewPerformerStore(),
lockChan: make(chan struct{}, 1),
}
return ret
@@ -106,8 +106,8 @@ func (db *Database) Ready() error {
// necessary migrations must be run separately using RunMigrations.
// Returns true if the database is new.
func (db *Database) Open(dbPath string) error {
db.writeMu.Lock()
defer db.writeMu.Unlock()
db.lockNoCtx()
defer db.unlock()
db.dbPath = dbPath
@@ -152,9 +152,36 @@ func (db *Database) Open(dbPath string) error {
return nil
}
// lock locks the database for writing.
// This method will block until the lock is acquired of the context is cancelled.
func (db *Database) lock(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
case db.lockChan <- struct{}{}:
return nil
}
}
// lock locks the database for writing. This method will block until the lock is acquired.
func (db *Database) lockNoCtx() {
db.lockChan <- struct{}{}
}
// unlock unlocks the database
func (db *Database) unlock() {
// will block the caller if the lock is not held, so check first
select {
case <-db.lockChan:
return
default:
panic("database is not locked")
}
}
func (db *Database) Close() error {
db.writeMu.Lock()
defer db.writeMu.Unlock()
db.lockNoCtx()
defer db.unlock()
if db.db != nil {
if err := db.db.Close(); err != nil {