Merge pull request #1429 from mattn/fix-vtable-handle-leak
Release vtable and cursor handles when SQLite destroys them
This commit is contained in:
21
callback.go
21
callback.go
@@ -131,6 +131,27 @@ func lookupHandle(handle unsafe.Pointer) any {
|
|||||||
return lookupHandleVal(handle).val
|
return lookupHandleVal(handle).val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deleteHandle releases a single handle created by newHandle. It is a no-op
|
||||||
|
// if the handle is unknown (e.g. already released).
|
||||||
|
func deleteHandle(handle unsafe.Pointer) {
|
||||||
|
handleLock.Lock()
|
||||||
|
defer handleLock.Unlock()
|
||||||
|
|
||||||
|
current := loadHandleVals()
|
||||||
|
if _, ok := current[handle]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
next := make(map[unsafe.Pointer]handleVal, len(current)-1)
|
||||||
|
for h, v := range current {
|
||||||
|
if h == handle {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
next[h] = v
|
||||||
|
}
|
||||||
|
handleVals.Store(next)
|
||||||
|
C.free(handle)
|
||||||
|
}
|
||||||
|
|
||||||
func deleteHandles(db *SQLiteConn) {
|
func deleteHandles(db *SQLiteConn) {
|
||||||
handleLock.Lock()
|
handleLock.Lock()
|
||||||
defer handleLock.Unlock()
|
defer handleLock.Unlock()
|
||||||
|
|||||||
@@ -423,6 +423,9 @@ func goVRelease(pVTab unsafe.Pointer, isDestroy C.int) *C.char {
|
|||||||
} else {
|
} else {
|
||||||
err = vt.vTab.Disconnect()
|
err = vt.vTab.Disconnect()
|
||||||
}
|
}
|
||||||
|
// The vtab is gone as far as SQLite is concerned regardless of the
|
||||||
|
// callback result, so release the handle either way.
|
||||||
|
deleteHandle(pVTab)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mPrintf("%s", err.Error())
|
return mPrintf("%s", err.Error())
|
||||||
}
|
}
|
||||||
@@ -492,6 +495,9 @@ func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char {
|
|||||||
func goVClose(pCursor unsafe.Pointer) *C.char {
|
func goVClose(pCursor unsafe.Pointer) *C.char {
|
||||||
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
|
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
|
||||||
err := vtc.vTabCursor.Close()
|
err := vtc.vTabCursor.Close()
|
||||||
|
// The cursor is gone as far as SQLite is concerned regardless of the
|
||||||
|
// callback result, so release the handle either way.
|
||||||
|
deleteHandle(pCursor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mPrintf("%s", err.Error())
|
return mPrintf("%s", err.Error())
|
||||||
}
|
}
|
||||||
@@ -502,6 +508,7 @@ func goVClose(pCursor unsafe.Pointer) *C.char {
|
|||||||
func goMDestroy(pClientData unsafe.Pointer) {
|
func goMDestroy(pClientData unsafe.Pointer) {
|
||||||
m := lookupHandle(pClientData).(*sqliteModule)
|
m := lookupHandle(pClientData).(*sqliteModule)
|
||||||
m.module.DestroyModule()
|
m.module.DestroyModule()
|
||||||
|
deleteHandle(pClientData)
|
||||||
}
|
}
|
||||||
|
|
||||||
//export goVFilter
|
//export goVFilter
|
||||||
|
|||||||
45
sqlite3_opt_vtable_leak_test.go
Normal file
45
sqlite3_opt_vtable_leak_test.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
//go:build sqlite_vtable
|
||||||
|
|
||||||
|
package sqlite3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var leakCheckDriverSeq int32
|
||||||
|
|
||||||
|
func TestVtabCursorHandleRelease(t *testing.T) {
|
||||||
|
// Use a unique driver name so repeated runs (e.g. -count=2) do not
|
||||||
|
// panic on duplicate registration.
|
||||||
|
driverName := fmt.Sprintf("sqlite3_HandleLeakCheck_%d", atomic.AddInt32(&leakCheckDriverSeq, 1))
|
||||||
|
sql.Register(driverName, &SQLiteDriver{
|
||||||
|
ConnectHook: func(conn *SQLiteConn) error {
|
||||||
|
return conn.CreateModule("test", &testModule{t: t, intarray: []int{1, 2, 3}})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
db, err := sql.Open(driverName, ":memory:")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
if _, err := db.Exec("CREATE VIRTUAL TABLE vtab USING test('1', 2, three)"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var before, after int
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
var n int
|
||||||
|
if err := db.QueryRow("SELECT count(*) FROM vtab").Scan(&n); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
before = len(loadHandleVals())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
after = len(loadHandleVals())
|
||||||
|
if after > before {
|
||||||
|
t.Fatalf("handle map grew from %d to %d over repeated cursor open/close", before, after)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user