Skip to content

Commit 3214ccd

Browse files
charlieviethdidzis
authored andcommitted
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 67836fe commit 3214ccd

File tree

2 files changed

+43
-23
lines changed

2 files changed

+43
-23
lines changed

sqlite3.go

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ type SQLiteStmt struct {
397397
s *C.sqlite3_stmt
398398
t string
399399
closed bool
400-
cls bool
400+
cls bool // True if the statement was created by SQLiteConn.Query
401401
}
402402

403403
// SQLiteResult implements sql.Result.
@@ -409,12 +409,12 @@ type SQLiteResult struct {
409409
// SQLiteRows implements driver.Rows.
410410
type SQLiteRows struct {
411411
s *SQLiteStmt
412-
nc int
412+
nc int32 // Number of columns
413+
cls bool // True if we need to close the parent statement in Close
413414
cols []string
414415
decltype []string
415-
cls bool
416-
closed bool
417416
ctx context.Context // no better alternative to pass context into Next() method
417+
closemu sync.Mutex
418418
}
419419

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

20322032
rows := &SQLiteRows{
20332033
s: s,
2034-
nc: int(C.sqlite3_column_count(s.s)),
2034+
nc: int32(C.sqlite3_column_count(s.s)),
2035+
cls: s.cls,
20352036
cols: nil,
20362037
decltype: nil,
2037-
cls: s.cls,
2038-
closed: false,
20392038
ctx: ctx,
20402039
}
2041-
runtime.SetFinalizer(rows, (*SQLiteRows).Close)
20422040

20432041
return rows, nil
20442042
}
@@ -2135,34 +2133,38 @@ func (s *SQLiteStmt) Readonly() bool {
21352133

21362134
// Close the rows.
21372135
func (rc *SQLiteRows) Close() error {
2138-
rc.s.mu.Lock()
2139-
if rc.s.closed || rc.closed {
2140-
rc.s.mu.Unlock()
2136+
rc.closemu.Lock()
2137+
defer rc.closemu.Unlock()
2138+
s := rc.s
2139+
if s == nil {
2140+
return nil
2141+
}
2142+
rc.s = nil // remove reference to SQLiteStmt
2143+
s.mu.Lock()
2144+
if s.closed {
2145+
s.mu.Unlock()
21412146
return nil
21422147
}
2143-
rc.closed = true
21442148
if rc.cls {
2145-
rc.s.mu.Unlock()
2146-
return rc.s.Close()
2149+
s.mu.Unlock()
2150+
return s.Close()
21472151
}
2148-
rv := C.sqlite3_reset(rc.s.s)
2152+
rv := C.sqlite3_reset(s.s)
21492153
if rv != C.SQLITE_OK {
2150-
rc.s.mu.Unlock()
2151-
return rc.s.c.lastError()
2154+
s.mu.Unlock()
2155+
return s.c.lastError()
21522156
}
2153-
rc.s.mu.Unlock()
2154-
rc.s = nil
2155-
runtime.SetFinalizer(rc, nil)
2157+
s.mu.Unlock()
21562158
return nil
21572159
}
21582160

21592161
// Columns return column names.
21602162
func (rc *SQLiteRows) Columns() []string {
21612163
rc.s.mu.Lock()
21622164
defer rc.s.mu.Unlock()
2163-
if rc.s.s != nil && rc.nc != len(rc.cols) {
2165+
if rc.s.s != nil && int(rc.nc) != len(rc.cols) {
21642166
rc.cols = make([]string, rc.nc)
2165-
for i := 0; i < rc.nc; i++ {
2167+
for i := 0; i < int(rc.nc); i++ {
21662168
rc.cols[i] = C.GoString(C.sqlite3_column_name(rc.s.s, C.int(i)))
21672169
}
21682170
}
@@ -2172,7 +2174,7 @@ func (rc *SQLiteRows) Columns() []string {
21722174
func (rc *SQLiteRows) declTypes() []string {
21732175
if rc.s.s != nil && rc.decltype == nil {
21742176
rc.decltype = make([]string, rc.nc)
2175-
for i := 0; i < rc.nc; i++ {
2177+
for i := 0; i < int(rc.nc); i++ {
21762178
rc.decltype[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(rc.s.s, C.int(i))))
21772179
}
21782180
}

sqlite3_test.go

Lines changed: 18 additions & 0 deletions
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)