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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
handleLock.Lock()
|
||||
defer handleLock.Unlock()
|
||||
|
||||
@@ -423,6 +423,9 @@ func goVRelease(pVTab unsafe.Pointer, isDestroy C.int) *C.char {
|
||||
} else {
|
||||
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 {
|
||||
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 {
|
||||
vtc := lookupHandle(pCursor).(*sqliteVTabCursor)
|
||||
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 {
|
||||
return mPrintf("%s", err.Error())
|
||||
}
|
||||
@@ -502,6 +508,7 @@ func goVClose(pCursor unsafe.Pointer) *C.char {
|
||||
func goMDestroy(pClientData unsafe.Pointer) {
|
||||
m := lookupHandle(pClientData).(*sqliteModule)
|
||||
m.module.DestroyModule()
|
||||
deleteHandle(pClientData)
|
||||
}
|
||||
|
||||
//export goVFilter
|
||||
|
||||
38
sqlite3_opt_vtable_leak_test.go
Normal file
38
sqlite3_opt_vtable_leak_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
//go:build sqlite_vtable
|
||||
|
||||
package sqlite3
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVtabCursorHandleRelease(t *testing.T) {
|
||||
sql.Register("sqlite3_HandleLeakCheck", &SQLiteDriver{
|
||||
ConnectHook: func(conn *SQLiteConn) error {
|
||||
return conn.CreateModule("test", &testModule{t: t, intarray: []int{1, 2, 3}})
|
||||
},
|
||||
})
|
||||
db, err := sql.Open("sqlite3_HandleLeakCheck", ":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