mirror of
https://github.com/actualbudget/actual.git
synced 2026-10-11 22:49:59 +00:00
[AI] Reduce webassembly heap by freeing all prepared statements (#9131)
* [AI] Free sql.js statements prepared by runQuery In the browser, runQuery prepared a new statement for every string query but only freed it for queries that fetch rows. sql.js keeps unfreed statements in the WASM heap until the database closes, so a cold load of a large budget (~500k spreadsheet cache writes) grew the heap by hundreds of MB. Free (or reset) the statement in all paths, including on error. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE * [AI] Add release note Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_016C82Fnx6cnESa3HUTuM9SE * bump --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: Michael Clark <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
Michael Clark
parent
358823017c
commit
0728f1a30c
@@ -193,4 +193,21 @@ describe('Web sqlite', () => {
|
||||
// @ts-expect-error Property 'id' does not exist on type 'unknown'
|
||||
expect(rows[0].id).toBe('id1');
|
||||
});
|
||||
|
||||
it('should free statements it prepares', async () => {
|
||||
const db = await openDatabase();
|
||||
execQuery(db, initSQL);
|
||||
const prepare = vi.spyOn(db, 'prepare');
|
||||
|
||||
runQuery(db, "INSERT INTO numbers (id, number) VALUES ('id1', 4)");
|
||||
runQuery(db, 'SELECT * FROM numbers', [], true);
|
||||
expect(() =>
|
||||
runQuery(db, "INSERT INTO numbers (id, number) VALUES ('id1', 5)"),
|
||||
).toThrow(/constraint failed/);
|
||||
|
||||
expect(prepare).toHaveBeenCalledTimes(3);
|
||||
for (const { value: statement } of prepare.mock.results) {
|
||||
expect(() => statement.run()).toThrow(/Statement closed/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -109,28 +109,31 @@ export function runQuery<T>(
|
||||
|
||||
const stmt = typeof sql === 'string' ? db.prepare(sql) : sql;
|
||||
|
||||
if (fetchAll) {
|
||||
try {
|
||||
stmt.bind(params);
|
||||
const rows = [];
|
||||
try {
|
||||
if (fetchAll) {
|
||||
try {
|
||||
stmt.bind(params);
|
||||
const rows = [];
|
||||
|
||||
while (stmt.step()) {
|
||||
rows.push(stmt.getAsObject());
|
||||
while (stmt.step()) {
|
||||
rows.push(stmt.getAsObject());
|
||||
}
|
||||
return rows;
|
||||
} catch (e) {
|
||||
logger.log(sql);
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (typeof sql === 'string') {
|
||||
stmt.free();
|
||||
} else {
|
||||
stmt.reset();
|
||||
}
|
||||
return rows;
|
||||
} catch (e) {
|
||||
logger.log(sql);
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
|
||||
stmt.run(params);
|
||||
return { changes: db.getRowsModified() };
|
||||
} finally {
|
||||
// sql.js keeps statements alive in the WASM heap until they are freed.
|
||||
if (typeof sql === 'string') {
|
||||
stmt.free();
|
||||
} else {
|
||||
stmt.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Bugfix
|
||||
authors: [mbrevda]
|
||||
---
|
||||
|
||||
Fix high memory use in the browser after opening a large budget
|
||||
Reference in New Issue
Block a user