db: only allow one open connection for in memory databases

sqlite3 in memory databases do not support concurrent writes. Limit
number of open connections to prevent race conditions.
This commit is contained in:
Eric Chiang 2016-02-22 16:30:48 -08:00
parent f51125f555
commit 22180c697f
2 changed files with 47 additions and 1 deletions

View file

@ -67,7 +67,14 @@ func NewConnection(cfg Config) (*gorp.DbMap, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// NOTE(ericchiang): sqlite does NOT work with SetMaxIdleConns. if u.Host == ":memory:" {
// NOTE(ericchiang): sqlite3 coordinates concurrent clients through file locks.
// In memory databases do not support concurrent calls. Limit the number of
// open connections to 1.
//
// See: https://www.sqlite.org/faq.html#q5
db.SetMaxOpenConns(1)
}
dialect = gorp.SqliteDialect{} dialect = gorp.SqliteDialect{}
default: default:
return nil, errors.New("unrecognized database driver") return nil, errors.New("unrecognized database driver")

39
db/conn_test.go Normal file
View file

@ -0,0 +1,39 @@
package db
import (
"sync"
"testing"
"github.com/coreos/dex/connector"
)
// TestConcurrentSqliteConns tests concurrent writes to a single in memory database.
func TestConcurrentSqliteConns(t *testing.T) {
dbMap := NewMemDB()
repo := NewConnectorConfigRepo(dbMap)
var (
once sync.Once
wg sync.WaitGroup
)
n := 1000
wg.Add(n)
for i := 0; i < n; i++ {
go func() {
configs := []connector.ConnectorConfig{
&connector.LocalConnectorConfig{ID: "local"},
}
// Setting connector configs both deletes and writes to a single table
// within a transaction.
if err := repo.Set(configs); err != nil {
// Don't print 1000 errors, only the first.
once.Do(func() {
t.Errorf("concurrent connections to sqlite3: %v", err)
})
}
wg.Done()
}()
}
wg.Wait()
}