From d03ed353dcc0776aeb707e4f13147f0f96dd8133 Mon Sep 17 00:00:00 2001 From: PThorpe92 Date: Wed, 29 Jan 2025 11:42:05 -0500 Subject: [PATCH] Free memory of strings + blobs created on the Rust side --- bindings/go/limbo_test.go | 2 +- bindings/go/types.go | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/bindings/go/limbo_test.go b/bindings/go/limbo_test.go index c6c256cf4..1a787a149 100644 --- a/bindings/go/limbo_test.go +++ b/bindings/go/limbo_test.go @@ -100,7 +100,7 @@ func TestQuery(t *testing.T) { t.Fatalf("Error scanning row: %v", err) } if a != i || b != rowsMap[i] || string(c) != rowsMap[i] { - t.Fatalf("Expected %d, %s, got %d, %s, %b", i, rowsMap[i], a, b, c) + t.Fatalf("Expected %d, %s, %s, got %d, %s, %b", i, rowsMap[i], rowsMap[i], a, b, c) } fmt.Println("RESULTS: ", a, b, string(c)) i++ diff --git a/bindings/go/types.go b/bindings/go/types.go index aee0f9bce..78fb96153 100644 --- a/bindings/go/types.go +++ b/bindings/go/types.go @@ -78,6 +78,7 @@ const ( FfiRowsGetValue string = "rows_get_value" FfiFreeColumns string = "free_columns" FfiFreeCString string = "free_string" + FfiFreeBlob string = "free_blob" ) // convert a namedValue slice into normal values until named parameters are supported @@ -147,9 +148,11 @@ func toGoValue(valPtr uintptr) interface{} { return *(*float64)(unsafe.Pointer(&val.Value)) case textVal: textPtr := *(*uintptr)(unsafe.Pointer(&val.Value)) + defer freeCString(textPtr) return GoString(textPtr) case blobVal: blobPtr := *(*uintptr)(unsafe.Pointer(&val.Value)) + defer freeBlob(blobPtr) return toGoBlob(blobPtr) case nullVal: return nil @@ -189,7 +192,34 @@ func toGoBlob(blobPtr uintptr) []byte { if blob.Data == 0 || blob.Len == 0 { return nil } - return unsafe.Slice((*byte)(unsafe.Pointer(blob.Data)), blob.Len) + data := unsafe.Slice((*byte)(unsafe.Pointer(blob.Data)), blob.Len) + copied := make([]byte, len(data)) + copy(copied, data) + return copied +} + +var freeBlobFunc func(uintptr) + +func freeBlob(blobPtr uintptr) { + if blobPtr == 0 { + return + } + if freeBlobFunc == nil { + getFfiFunc(&freeBlobFunc, FfiFreeBlob) + } + freeBlobFunc(blobPtr) +} + +var freeStringFunc func(uintptr) + +func freeCString(cstrPtr uintptr) { + if cstrPtr == 0 { + return + } + if freeStringFunc == nil { + getFfiFunc(&freeStringFunc, FfiFreeCString) + } + freeStringFunc(cstrPtr) } func cArrayToGoStrings(arrayPtr uintptr, length uint) []string { @@ -210,6 +240,8 @@ func cArrayToGoStrings(arrayPtr uintptr, length uint) []string { } // convert a Go slice of driver.Value to a slice of limboValue that can be sent over FFI +// for Blob types, we have to pin them so they are not garbage collected before they can be copied +// into a buffer on the Rust side, so we return a function to unpin them that can be deferred after this call func buildArgs(args []driver.Value) ([]limboValue, func(), error) { pinner := new(runtime.Pinner) argSlice := make([]limboValue, len(args))