Skip to content

Commit ea62cd9

Browse files
authored
test to see if memory doubles when using map on array (#1979)
* test to see if memory doubles when using map on array * use Deno.test() * adding timeouts to tests * fix(tests): fix integration tests to run properly in CI - Fix timeout handling in all tests to properly clear timers and prevent hanging processes and unhandled rejections - Fix reconnection.test.ts to use a properly resolving Promise instead of eternal await - Fix derive_array_leak.test.tsx to clamp array length to prevent negative values from throwing errors - Add 3-minute timeouts to pending-nursery, basic-persistence, and sync-schema-path tests * increase derive_array_leak test to 100 increments per click * fix(test): prevent reconnection test from hanging indefinitely * replace .all() with .iter(), hoping this helps with memory blowup * adjust timeouts * finalize prepared statements to prevent resource leak * fix malformed url * properly finalize prepared statements in * temp script to run test easily * skipping reconnection test for now * remove buffering from selectFacts to fix memory issue cubic identified that selectFacts was buffering all results before yielding, which defeats the purpose of using a generator and reintroduces the memory problem that bc0c716 originally fixed by switching from .all() to .iter(). this change: - removes the results array buffering - streams rows directly with yield toFact(row) - documents why finalize() cannot be used in a generator's finally block (finally blocks execute when generator returns, not when exhausted) the statement will be finalized when the store is closed. this is an acceptable trade-off given javascript generator limitations. addresses cubic's feedback on PR #1979
1 parent 4efbb28 commit ea62cd9

File tree

11 files changed

+736
-281
lines changed

11 files changed

+736
-281
lines changed

packages/memory/space-schema.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -348,18 +348,16 @@ function addToSelection(
348348
}
349349

350350
// Get the ValueEntry objects for the facts that match our selector
351-
function getMatchingFacts<Space extends MemorySpace>(
351+
function* getMatchingFacts<Space extends MemorySpace>(
352352
session: SpaceStoreSession<Space>,
353353
factSelector: FactSelector,
354354
): Iterable<IAttestation & { cause: CauseString; since: number }> {
355-
const results = [];
356355
for (const fact of selectFacts(session, factSelector)) {
357-
results.push({
356+
yield {
358357
value: fact.is,
359358
address: { id: fact.of, type: fact.the, path: [] },
360359
cause: fact.cause,
361360
since: fact.since,
362-
});
361+
};
363362
}
364-
return results;
365363
}

packages/memory/space.ts

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -570,15 +570,19 @@ export const selectFacts = function* <Space extends MemorySpace>(
570570
{ store }: Session<Space>,
571571
{ the, of, cause, is, since }: FactSelector,
572572
): Iterable<SelectedFact> {
573-
const rows = store.prepare(EXPORT).all({
574-
the: the === SelectAllString ? null : the,
575-
of: of === SelectAllString ? null : of,
576-
cause: cause === SelectAllString ? null : cause,
577-
is: is === undefined ? null : {},
578-
since: since ?? null,
579-
}) as StateRow[];
580-
581-
for (const row of rows) {
573+
const stmt = store.prepare(EXPORT);
574+
// Note: Cannot finalize() in a generator function's finally block because
575+
// the finally block runs when the generator returns (immediately), not when
576+
// it's exhausted. The statement will be finalized when the store is closed.
577+
for (
578+
const row of stmt.iter({
579+
the: the === SelectAllString ? null : the,
580+
of: of === SelectAllString ? null : of,
581+
cause: cause === SelectAllString ? null : cause,
582+
is: is === undefined ? null : {},
583+
since: since ?? null,
584+
}) as Iterable<StateRow>
585+
) {
582586
yield toFact(row);
583587
}
584588
};
@@ -587,14 +591,23 @@ export const selectFact = function <Space extends MemorySpace>(
587591
{ store }: Session<Space>,
588592
{ the, of, since }: { the: MIME; of: URI; since?: number },
589593
): SelectedFact | undefined {
590-
const rows = store.prepare(EXPORT).all({
591-
the: the,
592-
of: of,
593-
cause: null,
594-
is: null,
595-
since: since ?? null,
596-
}) as StateRow[];
597-
return (rows.length > 0) ? toFact(rows[0]) : undefined;
594+
const stmt = store.prepare(EXPORT);
595+
try {
596+
for (
597+
const row of stmt.iter({
598+
the: the,
599+
of: of,
600+
cause: null,
601+
is: null,
602+
since: since ?? null,
603+
}) as Iterable<StateRow>
604+
) {
605+
return toFact(row);
606+
}
607+
return undefined;
608+
} finally {
609+
stmt.finalize();
610+
}
598611
};
599612

600613
/**

packages/runner/integration/array_push.test.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,12 @@ const MEMORY_WS_URL = `${
2121
const SPACE_NAME = "runner_integration";
2222

2323
const TOTAL_COUNT = 20; // how many elements we push to the array
24-
const TIMEOUT_MS = 30000; // timeout for the test in ms
24+
const TIMEOUT_MS = 180000; // timeout for the test in ms (3 minutes)
2525

2626
console.log("Array Push Test");
2727
console.log(`Connecting to: ${MEMORY_WS_URL}`);
2828
console.log(`API URL: ${API_URL}`);
2929

30-
// Set up timeout
31-
const timeoutPromise = new Promise((_, reject) => {
32-
setTimeout(() => {
33-
reject(new Error(`Test timed out after ${TIMEOUT_MS}ms`));
34-
}, TIMEOUT_MS);
35-
});
36-
3730
// Main test function
3831
async function runTest() {
3932
const account = await Identity.fromPassphrase(ANYONE);
@@ -192,12 +185,23 @@ async function runTest() {
192185
}
193186

194187
// Run the test with timeout
195-
try {
196-
await Promise.race([runTest(), timeoutPromise]);
197-
console.log("Test completed successfully within timeout");
198-
Deno.exit(0);
199-
} catch (error) {
200-
const errorMessage = error instanceof Error ? error.message : String(error);
201-
console.error("Test failed:", errorMessage, (error as Error).stack);
202-
Deno.exit(1);
203-
}
188+
Deno.test({
189+
name: "array push test",
190+
fn: async () => {
191+
let timeoutHandle: number;
192+
const timeoutPromise = new Promise((_, reject) => {
193+
timeoutHandle = setTimeout(() => {
194+
reject(new Error(`Test timed out after ${TIMEOUT_MS}ms`));
195+
}, TIMEOUT_MS);
196+
});
197+
198+
try {
199+
await Promise.race([runTest(), timeoutPromise]);
200+
console.log("Test completed successfully within timeout");
201+
} finally {
202+
clearTimeout(timeoutHandle!);
203+
}
204+
},
205+
sanitizeResources: false,
206+
sanitizeOps: false,
207+
});

packages/runner/integration/basic-persistence.test.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const identity = await Identity.fromPassphrase("test operator", keyConfig);
1515

1616
console.log("\n=== TEST: Simple object persistence ===");
1717

18+
const TIMEOUT_MS = 180000; // 3 minutes timeout
19+
1820
async function test() {
1921
// First runtime - save data
2022
const runtime1 = new Runtime({
@@ -67,16 +69,37 @@ async function test() {
6769
return [cell1Contents, cell2Contents];
6870
}
6971

70-
for (let i: number = 1; i <= 20; i++) {
71-
const [result1, result2] = await test();
72-
if (!deepEqual(result1, result2)) {
73-
console.error("Mismatched results for iteration", i, result1, result2);
74-
Deno.exit(1);
75-
}
76-
if (i % 5 == 0) {
77-
console.log("completed", i, "...");
72+
async function runTest() {
73+
for (let i: number = 1; i <= 20; i++) {
74+
const [result1, result2] = await test();
75+
if (!deepEqual(result1, result2)) {
76+
console.error("Mismatched results for iteration", i, result1, result2);
77+
throw new Error(`Mismatched results for iteration ${i}`);
78+
}
79+
if (i % 5 == 0) {
80+
console.log("completed", i, "...");
81+
}
7882
}
83+
84+
console.log("\nDone");
7985
}
8086

81-
console.log("\nDone");
82-
Deno.exit(0);
87+
Deno.test({
88+
name: "basic persistence test",
89+
fn: async () => {
90+
let timeoutHandle: number;
91+
const timeoutPromise = new Promise((_, reject) => {
92+
timeoutHandle = setTimeout(() => {
93+
reject(new Error(`Test timed out after ${TIMEOUT_MS}ms`));
94+
}, TIMEOUT_MS);
95+
});
96+
97+
try {
98+
await Promise.race([runTest(), timeoutPromise]);
99+
} finally {
100+
clearTimeout(timeoutHandle!);
101+
}
102+
},
103+
sanitizeResources: false,
104+
sanitizeOps: false,
105+
});

0 commit comments

Comments
 (0)