Memory leak in vec0Filter_knn: knn_data not freed across filter calls (v0.1.6)
Memory leak in vec0Filter_knn: knn_data and its internal buffers (v0.1.6)
Summary
When running KNN queries via the vec0 virtual table, vec0Filter_knn allocates a vec0_query_knn_data struct (and its rowids / distances buffers), but the memory is not reliably freed across query lifecycles. AddressSanitizer (gcc 13, Linux) consistently reports a leak of 48 bytes per query — matching sizeof(struct vec0_query_knn_data) — scaling linearly with query count.
Environment
- sqlite-vec v0.1.6 (vendored amalgamation,
sqlite-vec.c+sqlite-vec.h) - SQLite 3.46.1 (amalgamation)
- Linux (GCC 13.3.0), AddressSanitizer + LeakSanitizer enabled
- Build flags:
-fsanitize=address -fno-omit-frame-pointer -g
Reproduction
- Register the
vec0module and create a vtab:CREATE VIRTUAL TABLE memvec USING vec0( embedding float[128] distance_metric=cosine ); INSERT INTO memvec(rowid, embedding) VALUES (1, vec_f32('[...]')), ...; - Run a KNN query N times in a loop:
SELECT rowid, distance FROM memvec WHERE embedding MATCH vec_f32('[...]') AND k = 5 ORDER BY distance; - Run under ASan + LeakSanitizer.
Observed behavior
- 48 × N bytes leaked in N allocations
- Stack (top frames):
#0 malloc #1 sqlite3MemMalloc (sqlite3.c:26781) #2 mallocWithAlarm (sqlite3.c:30492) #3 sqlite3Malloc (sqlite3.c:30538) #4 sqlite3_malloc (sqlite3.c:30556) #5 vec0Filter_knn (sqlite-vec.c:6819) #6 vec0Filter (sqlite-vec.c:7210) #7 sqlite3VdbeExec (sqlite3.c:101441) #8 sqlite3Step (sqlite3.c:91222)
Analysis
At sqlite-vec.c:6819:
knn_data = sqlite3_malloc(sizeof(*knn_data));
if (!knn_data) {
return SQLITE_NOMEM;
}The local knn_data is allocated and (on success paths at L6896 and L7058) assigned to pCur->knn_data. However, I observe 48-byte-per-query leaks in a scenario where:
- Queries succeed (k > 0, rc = SQLITE_OK)
- Cursor is closed via the standard SQLite API path
Looking at vec0Cursor_reset (L4620–4632), there is correct cleanup:
if (pCur->knn_data) {
vec0_query_knn_data_clear(pCur->knn_data);
sqlite3_free(pCur->knn_data);
pCur->knn_data = NULL;
}And vec0_query_knn_data_clear frees knn_data->rowids / knn_data->distances. So the cleanup exists. Two candidate root causes:
Hypothesis A: vec0Cursor_reset is not invoked before the cursor is destroyed in some codepath. Possibly vec0Close does not call vec0Cursor_reset (or calls it conditionally).
Hypothesis B: On repeat filter calls on the same cursor (e.g. xFilter called again without full reset between), the old pCur->knn_data is overwritten by the new allocation at L6819 without freeing the previous one. Looking at the function, there is no if (pCur->knn_data) { free; } check before the new allocation.
Proposed fix (hypothesis B)
At the start of vec0Filter_knn (or even at its cleanup label), first release any previously held state:
int vec0Filter_knn(vec0_cursor *pCur, vec0_vtab *p, int idxNum,
const char *idxStr, int argc, sqlite3_value **argv) {
/* NEW: ensure previous query state is released */
if (pCur->knn_data) {
vec0_query_knn_data_clear(pCur->knn_data);
sqlite3_free(pCur->knn_data);
pCur->knn_data = NULL;
}
/* ... existing code ... */
knn_data = sqlite3_malloc(sizeof(*knn_data));
...
}Same pattern applies for the other vec0Filter_* paths (fullscan, point) to be safe.
Impact
Any application that holds cursors open and refiltered (e.g. prepared statements reset + re-executed with new parameters) leaks steadily. In our application a typical session leaks 48 bytes × N KNN queries, so 1500 queries = 72 KB per session.
Additional info
- The bug also leaks
knn_data->rowidsandknn_data->distances(variable-sized), which the report amounts don't show because gcc ASan collapses similar small allocations; total leak is at least48 + N*8 + N*4bytes per query for a K-size nearest-neighbor result. - Happy to open a PR with the proposed fix and a unit test that reproduces the leak via repeated filter calls. Let me know.
Related code references
- Allocation:
sqlite-vec.c:6819 - Assignment to cursor:
sqlite-vec.c:6896, 7058 - Cleanup helper:
sqlite-vec.c:4620-4632 - Struct definition:
sqlite-vec.c:4562
Source: asg017/sqlite-vec