Skip to content

Commit c61eeb5

Browse files
charlieviethmattn
authored andcommittedDec 9, 2024·
remove superfluous use of runtime.SetFinalizer on SQLiteRows
The commit removes the use of runtime.SetFinalizer to finalize SQLiteRows since only serves to close the associated SQLiteStmt which already has a registered finalizer. It also fixes a race and potential panic in SQLiteRows.Close around the SQLiteRows.s field (*SQLiteStmt) which is accessed without a mutex being held, but modified with it held (null'd out). Further the mutex we are holding is that of the SQLiteStmt so a subsequent call to Close will cause a panic sine it'll attempt to dereference a nil field. The fix here is to add a mutex for closing to SQLiteRows. Since we now also set the s field to nil when closing this commit removes the "closed" field (since checking if s is nil is the same) and also changes the type of "nc" (number of columns) to an int32 so that we can pack the nc and cls fields, and add the close mutex without making the struct any bigger. ``` goos: darwin goarch: arm64 pkg: github.com/charlievieth/go-sqlite3 cpu: Apple M4 Pro │ x1.txt │ x4.txt │ │ sec/op │ sec/op vs base │ Suite/BenchmarkExec/Params-14 719.2n ± 2% 716.9n ± 1% ~ (p=0.897 n=10) Suite/BenchmarkExec/NoParams-14 506.5n ± 3% 500.1n ± 0% -1.25% (p=0.002 n=10) Suite/BenchmarkExecContext/Params-14 1.584µ ± 0% 1.567µ ± 1% -1.07% (p=0.007 n=10) Suite/BenchmarkExecContext/NoParams-14 1.524µ ± 1% 1.524µ ± 1% ~ (p=0.539 n=10) Suite/BenchmarkExecStep-14 443.9µ ± 3% 441.4µ ± 0% -0.55% (p=0.011 n=10) Suite/BenchmarkExecContextStep-14 447.8µ ± 1% 442.9µ ± 0% -1.10% (p=0.000 n=10) Suite/BenchmarkExecTx-14 1.643µ ± 1% 1.640µ ± 0% ~ (p=0.642 n=10) Suite/BenchmarkQuery-14 1.968µ ± 3% 1.821µ ± 1% -7.52% (p=0.000 n=10) Suite/BenchmarkQuerySimple-14 1.207µ ± 2% 1.040µ ± 1% -13.84% (p=0.000 n=10) Suite/BenchmarkQueryContext/Background-14 2.400µ ± 1% 2.320µ ± 0% -3.31% (p=0.000 n=10) Suite/BenchmarkQueryContext/WithCancel-14 8.847µ ± 5% 8.512µ ± 4% -3.79% (p=0.007 n=10) Suite/BenchmarkParams-14 2.131µ ± 2% 1.967µ ± 1% -7.70% (p=0.000 n=10) Suite/BenchmarkStmt-14 1.444µ ± 1% 1.359µ ± 1% -5.89% (p=0.000 n=10) Suite/BenchmarkRows-14 61.57µ ± 1% 60.24µ ± 1% -2.16% (p=0.000 n=10) Suite/BenchmarkStmtRows-14 60.15µ ± 1% 59.08µ ± 1% -1.78% (p=0.000 n=10) Suite/BenchmarkQueryParallel-14 960.9n ± 1% 420.8n ± 2% -56.21% (p=0.000 n=10) geomean 4.795µ 4.430µ -7.62% ```
1 parent ab13d63 commit c61eeb5

File tree

2 files changed

+43
-23
lines changed

2 files changed

+43
-23
lines changed
 

‎sqlite3.go

+25-23
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ type SQLiteStmt struct {
381381
s *C.sqlite3_stmt
382382
t string
383383
closed bool
384-
cls bool
384+
cls bool // True if the statement was created by SQLiteConn.Query
385385
}
386386

387387
// SQLiteResult implements sql.Result.
@@ -393,12 +393,12 @@ type SQLiteResult struct {
393393
// SQLiteRows implements driver.Rows.
394394
type SQLiteRows struct {
395395
s *SQLiteStmt
396-
nc int
396+
nc int32 // Number of columns
397+
cls bool // True if we need to close the parent statement in Close
397398
cols []string
398399
decltype []string
399-
cls bool
400-
closed bool
401400
ctx context.Context // no better alternative to pass context into Next() method
401+
closemu sync.Mutex
402402
}
403403

404404
type functionInfo struct {
@@ -2008,14 +2008,12 @@ func (s *SQLiteStmt) query(ctx context.Context, args []driver.NamedValue) (drive
20082008

20092009
rows := &SQLiteRows{
20102010
s: s,
2011-
nc: int(C.sqlite3_column_count(s.s)),
2011+
nc: int32(C.sqlite3_column_count(s.s)),
2012+
cls: s.cls,
20122013
cols: nil,
20132014
decltype: nil,
2014-
cls: s.cls,
2015-
closed: false,
20162015
ctx: ctx,
20172016
}
2018-
runtime.SetFinalizer(rows, (*SQLiteRows).Close)
20192017

20202018
return rows, nil
20212019
}
@@ -2112,34 +2110,38 @@ func (s *SQLiteStmt) Readonly() bool {
21122110

21132111
// Close the rows.
21142112
func (rc *SQLiteRows) Close() error {
2115-
rc.s.mu.Lock()
2116-
if rc.s.closed || rc.closed {
2117-
rc.s.mu.Unlock()
2113+
rc.closemu.Lock()
2114+
defer rc.closemu.Unlock()
2115+
s := rc.s
2116+
if s == nil {
2117+
return nil
2118+
}
2119+
rc.s = nil // remove reference to SQLiteStmt
2120+
s.mu.Lock()
2121+
if s.closed {
2122+
s.mu.Unlock()
21182123
return nil
21192124
}
2120-
rc.closed = true
21212125
if rc.cls {
2122-
rc.s.mu.Unlock()
2123-
return rc.s.Close()
2126+
s.mu.Unlock()
2127+
return s.Close()
21242128
}
2125-
rv := C.sqlite3_reset(rc.s.s)
2129+
rv := C.sqlite3_reset(s.s)
21262130
if rv != C.SQLITE_OK {
2127-
rc.s.mu.Unlock()
2128-
return rc.s.c.lastError()
2131+
s.mu.Unlock()
2132+
return s.c.lastError()
21292133
}
2130-
rc.s.mu.Unlock()
2131-
rc.s = nil
2132-
runtime.SetFinalizer(rc, nil)
2134+
s.mu.Unlock()
21332135
return nil
21342136
}
21352137

21362138
// Columns return column names.
21372139
func (rc *SQLiteRows) Columns() []string {
21382140
rc.s.mu.Lock()
21392141
defer rc.s.mu.Unlock()
2140-
if rc.s.s != nil && rc.nc != len(rc.cols) {
2142+
if rc.s.s != nil && int(rc.nc) != len(rc.cols) {
21412143
rc.cols = make([]string, rc.nc)
2142-
for i := 0; i < rc.nc; i++ {
2144+
for i := 0; i < int(rc.nc); i++ {
21432145
rc.cols[i] = C.GoString(C.sqlite3_column_name(rc.s.s, C.int(i)))
21442146
}
21452147
}
@@ -2149,7 +2151,7 @@ func (rc *SQLiteRows) Columns() []string {
21492151
func (rc *SQLiteRows) declTypes() []string {
21502152
if rc.s.s != nil && rc.decltype == nil {
21512153
rc.decltype = make([]string, rc.nc)
2152-
for i := 0; i < rc.nc; i++ {
2154+
for i := 0; i < int(rc.nc); i++ {
21532155
rc.decltype[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(rc.s.s, C.int(i))))
21542156
}
21552157
}

‎sqlite3_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,7 @@ var benchmarks = []testing.InternalBenchmark{
21112111
{Name: "BenchmarkStmt", F: benchmarkStmt},
21122112
{Name: "BenchmarkRows", F: benchmarkRows},
21132113
{Name: "BenchmarkStmtRows", F: benchmarkStmtRows},
2114+
{Name: "BenchmarkQueryParallel", F: benchmarkQueryParallel},
21142115
}
21152116

21162117
func (db *TestDB) mustExec(sql string, args ...any) sql.Result {
@@ -2568,3 +2569,20 @@ func benchmarkStmtRows(b *testing.B) {
25682569
}
25692570
}
25702571
}
2572+
2573+
func benchmarkQueryParallel(b *testing.B) {
2574+
b.RunParallel(func(pb *testing.PB) {
2575+
db, err := sql.Open("sqlite3", ":memory:")
2576+
if err != nil {
2577+
panic(err)
2578+
}
2579+
db.SetMaxOpenConns(runtime.NumCPU())
2580+
defer db.Close()
2581+
var i int64
2582+
for pb.Next() {
2583+
if err := db.QueryRow("SELECT 1, 2, 3, 4").Scan(&i, &i, &i, &i); err != nil {
2584+
panic(err)
2585+
}
2586+
}
2587+
})
2588+
}

0 commit comments

Comments
 (0)
Please sign in to comment.