Report memory as a breakdown, and fix OOM detection - #70
Merged
Conversation
V8 only traps (SIGTRAP) when the heap limit is too small to boot, which is why `memory: 1` was detected. Every realistic limit exhausts the heap through the normal OOM path and exits with SIGABRT, so it escaped as a raw ChildProcessError and never surfaced as MemoryError. The existing test only covered `memory: 1`, the single value that traps, so it passed while every production limit was broken. Gate the abort on V8's own "out of memory" message so an unrelated abort is not mislabelled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CTJQQb6p1UWdkireDjCVri
profiling.memory was a single RSS sample taken when the function returned. No single number is correct: RSS over-reports (a no-op function reported ~43MB of Node.js baseline) and under-reports (300MB of untouched typed arrays stayed invisible, because pages that are never written to never become resident). Report the fields that answer different questions instead: total RSS of the isolate process, baseline included used total minus the pre-execution baseline heap V8 heap, the only field the `memory` limit bounds external off-heap Buffer/ArrayBuffer, which the limit misses Also correct the README resource-limit example, which claimed 78MB of Uint8Array trips `memory: 64`. It does not: typed arrays are off-heap, so it returns cleanly with external=79.7MB. Replaced with on-heap growth, which the limit does bound. BREAKING CHANGE: profiling.memory is an object instead of a number. Callers reading it as a number should read profiling.memory.used for the memory attributable to the function, or .total for the previous RSS value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CTJQQb6p1UWdkireDjCVri
Coverage Report for CI Build 29565032313Warning No base build found for commit Coverage: 95.163%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsRequires a base build to compare against. How to fix this → Coverage Stats
💛 - Coveralls |
1 similar comment
Coverage Report for CI Build 29565032313Warning No base build found for commit Coverage: 95.163%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsRequires a base build to compare against. How to fix this → Coverage Stats
💛 - Coveralls |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
profiling.memorywas a singleprocess.memoryUsage().rsssample taken when the function returned. No single number is correct here, and this one was wrong in both directions:() => 1 + 1reported 43.3 MB, because that is the Node.js runtime baseline. Nothing was attributable to the function.It was also documented as "Peak RSS", which it is not: it is sampled once, in
respond(), after the function settles. There is no high-water tracking.What
profiling.memorybecomes a breakdown, where each field answers a different question:totalusedtotalminus the baseline measured before the function ranheapmemorylimit boundsexternalBuffer/ArrayBuffer. Not bounded by thememorylimitMeasured across workloads, under
memory: 16:memoryusedheapexternal() => 1 + 1The last row is the case that justifies the split: the old number looks innocent while the process holds 300 MB. Only
externalcatches it.Bug fix:
MemoryErrornever fired at a realistic limitIncluded as a separate commit, because it stands on its own.
V8 only raises
SIGTRAPwhen the heap limit is too small to boot the heap. Every realistic limit exhausts the heap through the normal OOM path and exits withSIGABRT, which was not checked, so it escaped as a rawChildProcessErrorinstead ofMemoryError.memory18/16/64/128The existing test only covered
memory: 1, the single value that traps, so the suite stayed green over a broken path. The abort is gated on V8's ownout of memorymessage so an unrelated abort is not mislabelled, and there is a test asserting that.Docs correction
The README's resource-limit example claimed 78 MB of
Uint8Arraytrips{ memory: 64 }. It does not. Typed arrays are off-heap, so it returns cleanly withexternal=79.7MB. Verified:Replaced with on-heap growth, which the limit does bound, and added a note that
memorybounds the heap only.BREAKING CHANGE
profiling.memoryis an object instead of a number. Callers reading it as a number wantprofiling.memory.used, or.totalfor the previous value.Tests
40 passed. Both new tests were confirmed to fail against the old code before the fix (the OOM one fails withChildProcessError), so they are real guards rather than green-by-construction.🤖 Generated with Claude Code
Note
Medium Risk
Breaking API change for
profiling.memoryconsumers and altered error classification for child process exits; core isolate profiling and limit behavior.Overview
Breaking:
profiling.memoryis now an object (total,used,heap,external) instead of a single RSS number. The isolate template captures a pre-run RSS baseline and reports attributableusedmemory plus V8heapand off-heapexternal.OOM handling now treats SIGABRT with V8’s “out of memory” message as
MemoryError, not only SIGTRAP (tiny limits). Unrelated aborts stay non-MemoryError.README and the memory-limit example are updated: heap-bound string growth replaces the misleading typed-array demo, with a note that
memorylimits V8 heap only, notexternal.Reviewed by Cursor Bugbot for commit 8f00253. Bugbot is set up for automated code reviews on this repo. Configure here.