From fa3fc5888a953eb9f69827cb864c72b21e53d2c7 Mon Sep 17 00:00:00 2001 From: Project516 <138796702+Project516@users.noreply.github.com> Date: Thu, 5 Nov 2026 13:37:15 -0600 Subject: [PATCH] Throw OutOfMemoryError only after two readings over the cap, and free the heap before HeapCap prints --- src/runtime.ts | 15 ++++++++++----- test/java/HeapCap.java | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/runtime.ts b/src/runtime.ts index 4e0ca53..b3fe528 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -383,9 +383,9 @@ export function setMaxHeap(bytes: number) { explicitMaxHeap = true; } export function hasMaxHeap() { return explicitMaxHeap; } -let allocationCheck: { cap: number; step: number; allocated: number; baseline: number; enabled: boolean; usage: () => number; gc?: () => void } | null = null; +let allocationCheck: { cap: number; step: number; allocated: number; baseline: number; enabled: boolean; over: boolean; usage: () => number; gc?: () => void } | null = null; export function setAllocationCheck(cap: number, usage: () => number, gc?: () => void) { - allocationCheck = { cap, step: Math.min(cap, Math.max(4 << 20, cap / 16)), allocated: 0, baseline: 0, enabled: false, usage, gc }; + allocationCheck = { cap, step: Math.min(cap, Math.max(4 << 20, cap / 16)), allocated: 0, baseline: 0, enabled: false, over: false, usage, gc }; } export function clearAllocationCheck() { allocationCheck = null; } export function resetAllocationCheck() { @@ -400,10 +400,15 @@ function allocated(bytes: number) { check.allocated += bytes; if (check.gc ? check.allocated < check.step : check.allocated <= check.cap) return; check.allocated = 0; - if (check.usage() - check.baseline > check.cap) { - check.gc?.(); - if (check.usage() - check.baseline > check.cap) throw new PendingThrow("java/lang/OutOfMemoryError", "Java heap space"); + if (check.usage() - check.baseline > check.cap) check.gc?.(); + // node frees array buffers after a collection, not during it, so one reading + // over the cap can still count garbage: throw on the second in a row + const over = check.usage() - check.baseline > check.cap; + if (over && check.over) { + check.over = false; + throw new PendingThrow("java/lang/OutOfMemoryError", "Java heap space"); } + check.over = over; } const ELEM_SIZE: Record = { Z: 1, B: 1, C: 2, S: 2, I: 4, F: 4, J: 8, D: 8, L: 4 }; diff --git a/test/java/HeapCap.java b/test/java/HeapCap.java index bc7b569..039d7b6 100644 --- a/test/java/HeapCap.java +++ b/test/java/HeapCap.java @@ -8,9 +8,9 @@ public static void main(String[] args) { try { while (true) keep.add(new byte[256 * 1024]); } catch (OutOfMemoryError e) { - System.out.println("OutOfMemoryError: " + e.getMessage()); + // free the heap before printing: building the message allocates too keep.clear(); - System.gc(); + System.out.println("OutOfMemoryError: " + e.getMessage()); } for (int i = 0; i < 16; i++) keep.add(new byte[256 * 1024]); System.out.println("continued");