You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(agentdb): cache prepared statements to plug sql.js leak (ruvnet#144)
Both sql.js wrappers in db-fallback.ts (SqlJsDatabase and
wrapExistingSqlJsDatabase) tracked every prepare() call in
activeStatements and only removed entries on finalize() or error.
The idiomatic better-sqlite3 pattern db.prepare(sql).run(...) — which
relies on V8 to GC the handle — leaks unbounded under sql.js because
WASM-backed Statement objects need explicit .free().
Production observation (Cloud Run mcp-bridge): ~3 statements/s growth,
hit 621 within minutes:
[ruflo] ⚠️ Detected 562 active SQL statements - possible memory leak
[ruflo] ⚠️ Detected 612 active SQL statements - possible memory leak
[ruflo] ⚠️ Detected 621 active SQL statements - possible memory leak
Fix: cache wrappers by SQL text. Repeated prepare(sameSql) returns the
same wrapper; activeStatements.size is now bounded by the count of
distinct SQL strings. Safe under single-threaded JS — bind/step/reset
are synchronous so no two run/get/all calls can interleave on a shared
stmt. finalize() is now a no-op on cached wrappers (close() owns
teardown); auto-free-on-error also evicts from cache so a broken stmt
isn't reused.
Smoke test (5000 prepare(INSERT).run + 1 prepare(SELECT).get):
activeStatements_size: 2 (was 5001 pre-patch)
statementCache_size: 2
post_close_active: 0
post_close_cache: 0
Typecheck: clean.
Test failures observed in repo are pre-existing (jest stubs in vitest
project, hnsw zero-vector edge case) — unrelated to this change.
0 commit comments