1
0
Fork 0

Limit in-memory connection pool to 1 connection

https://github.com/mattn/go-sqlite3/issues/959#issuecomment-890283264
master
Philip O'Toole 3 years ago
parent ea45d3b2c0
commit 1ee7a9eb17

@ -20,9 +20,7 @@ import (
const bkDelay = 250
const (
connMaxIdleTime = time.Duration(30 * time.Second)
maxOpenConns = 128
maxIdleConns = 16
onDiskMaxOpenConns = 32
fkChecks = "PRAGMA foreign_keys"
fkChecksEnabled = "PRAGMA foreign_keys=ON"
@ -93,22 +91,44 @@ type Rows struct {
// Open opens a file-based database, creating it if it does not exist.
func Open(dbPath string) (*DB, error) {
return open(fqdsn(dbPath, ""))
db, err := open(fqdsn(dbPath, ""))
if err != nil {
return nil, err
}
db.db.SetMaxOpenConns(onDiskMaxOpenConns)
return db, nil
}
// OpenWithDSN opens a file-based database, creating it if it does not exist.
func OpenWithDSN(dbPath, dsn string) (*DB, error) {
return open(fqdsn(dbPath, dsn))
db, err := open(fqdsn(dbPath, dsn))
if err != nil {
return nil, err
}
db.db.SetMaxOpenConns(onDiskMaxOpenConns)
return db, nil
}
// OpenInMemory opens an in-memory database.
func OpenInMemory() (*DB, error) {
return open(fqdsn(randomInMemoryDB(), ""))
db, err := open(fqdsn(randomInMemoryDB(), ""))
if err != nil {
return nil, err
}
// In-memory databases do not support connection pooling.
db.db.SetMaxOpenConns(1)
return db, nil
}
// OpenInMemoryWithDSN opens an in-memory database with a specific DSN.
func OpenInMemoryWithDSN(dsn string) (*DB, error) {
return open(fqdsn(randomInMemoryDB(), dsn))
db, err := open(fqdsn(randomInMemoryDB(), dsn))
if err != nil {
return nil, err
}
// In-memory databases do not support connection pooling.
db.db.SetMaxOpenConns(1)
return db, nil
}
// LoadInMemoryWithDSN loads an in-memory database with that at the path,
@ -198,13 +218,6 @@ func open(dbPath string) (*DB, error) {
return nil, err
}
// Configure connection pool parameters such that rqlite behavior
// remains generally similar to previous versions that didn't use
// a pool. The pool may be configurable in a future release
db.SetConnMaxIdleTime(connMaxIdleTime)
db.SetMaxIdleConns(maxIdleConns)
db.SetMaxOpenConns(maxOpenConns)
// Ensure database is basically healthy.
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("Ping database: %s", err.Error())

@ -7,7 +7,9 @@ import (
"os"
"path"
"strings"
"sync"
"testing"
"time"
"github.com/rqlite/rqlite/command"
"github.com/rqlite/rqlite/testdata/chinook"
@ -1188,6 +1190,112 @@ func Test_DumpMemory(t *testing.T) {
}
}
// Test_ParallelOperationsInMemory runs multiple accesses concurrently, ensuring
// that correct results are returned in every goroutine. It's not 100% that this
// test would bring out a bug, but it's almost 100%.
//
// See https://github.com/mattn/go-sqlite3/issues/959#issuecomment-890283264
func Test_ParallelOperationsInMemory(t *testing.T) {
var err error
db := mustCreateInMemoryDatabase()
defer db.Close()
_, err = db.ExecuteStringStmt("CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
_, err = db.ExecuteStringStmt("CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
_, err = db.ExecuteStringStmt("CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)")
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
var rows []*Rows
rows, err = db.QueryStringStmt(`SELECT sql FROM sqlite_master`)
if err != nil {
t.Fatalf("failed to query for schema after creation: %s", err.Error())
}
if exp, got := `[{"columns":["sql"],"types":["text"],"values":[["CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"]]}]`, asJSON(rows); exp != got {
t.Fatalf("schema not as expected during after creation, exp %s, got %s", exp, got)
}
var wg sync.WaitGroup
wg.Add(3)
foo := make(chan time.Time)
bar := make(chan time.Time)
qux := make(chan time.Time)
done := make(chan bool)
ticker := time.NewTicker(1 * time.Millisecond)
go func() {
for {
select {
case t := <-ticker.C:
foo <- t
bar <- t
qux <- t
case <-done:
close(foo)
close(bar)
close(qux)
return
}
}
}()
go func() {
defer wg.Done()
for range foo {
_, err = db.ExecuteStringStmt(`INSERT INTO foo(id, name) VALUES(1, "fiona")`)
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
}
}()
go func() {
defer wg.Done()
for range bar {
_, err = db.ExecuteStringStmt(`INSERT INTO bar(id, name) VALUES(1, "fiona")`)
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
}
}()
go func() {
defer wg.Done()
for range qux {
_, err = db.ExecuteStringStmt(`INSERT INTO qux(id, name) VALUES(1, "fiona")`)
if err != nil {
t.Fatalf("failed to create table: %s", err.Error())
}
}
}()
var n int
for {
rows, err = db.QueryStringStmt(`SELECT sql FROM sqlite_master`)
if err != nil {
t.Fatalf("failed to query for schema during goroutine execution: %s", err.Error())
}
n++
if exp, got := `[{"columns":["sql"],"types":["text"],"values":[["CREATE TABLE foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE bar (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"],["CREATE TABLE qux (id INTEGER NOT NULL PRIMARY KEY, name TEXT)"]]}]`, asJSON(rows); exp != got {
t.Fatalf("schema not as expected during goroutine execution, exp %s, got %s, after %d queries", exp, got, n)
}
if n == 500000 {
break
}
}
close(done)
wg.Wait()
}
func mustCreateDatabase() (*DB, string) {
var err error
f := mustTempFile()

Loading…
Cancel
Save