Skip to content

heap: auto near oom headroom extension - #401

Merged
szegedi merged 2 commits into
mainfrom
ishabi/near-oom-callback
Aug 26, 2026
Merged

heap: auto near oom headroom extension#401
szegedi merged 2 commits into
mainfrom
ishabi/near-oom-callback

Conversation

@IlyasShabi

@IlyasShabi IlyasShabi commented Aug 23, 2026

Copy link
Copy Markdown

Current behaviour

When V8 signals it is near the heap limit, the callback:

  1. raises the heap limit by a fixed number of bytes heapLimitExtensionSize
  2. captures an allocation profile.
  3. writes it to a temp file and spawns a separate process to collect it.

The problem is step 1. That number is a constant and nothing ties it to what V8 actually needs to finish one more GC while we capture. When it is too small the allocation still isn't satisfied, so V8 calls us again, and every one of those calls redoes the whole thing: heap stats, GetAllocationProfile(), translation, another spawned process. Repetitive work, and each round burns the extension budget for nothing.

That is also why the default kept creeping up (10 MiB, then 20 MiB) without any real basis.

Meanwhile V8 already reserves headroom beyond the old generation and will happily tell us how much. Why guess a constant instead of using what is already there?

Proposed solution

Inspired by how Node.js sizes its own near-OOM heap snapshot which extends by one young generation rather than a constant.

heapLimitExtensionSize now also accepts 'auto', and when set:

  1. On the first near-OOM event we read heap_size_limit() current_heap_limit, which is exactly one maximum young generation and cache it for later events.
  2. That amount is granted instead of a constant, so the extension matches what V8 needs to complete the GC and let the capture finish.
  3. The addition is overflow-safe.

This is not a breaking change. Numeric sizes behave exactly as before, including 0 meaning "grant nothing", and the constant reentrant rescue that lets an in-progress capture finish. 'auto' is opt-in.

@github-actions

Copy link
Copy Markdown

Overall package size

Self size: 2.55 MB
Deduped: 3.26 MB
No deduping: 3.26 MB

Dependency sizes | name | version | self size | total size | |------|---------|-----------|------------| | pprof-format | 2.3.1 | 504.33 kB | 504.33 kB | | source-map | 0.8.0 | 185.66 kB | 185.66 kB | | node-gyp-build | 4.8.4 | 13.86 kB | 13.86 kB |

🤖 This report was automatically generated by heaviest-objects-in-the-universe

@IlyasShabi IlyasShabi added the semver-minor Usually minor non-breaking improvements label Aug 24, 2026
@IlyasShabi IlyasShabi added semver-patch Bug or security fixes, mainly and removed semver-minor Usually minor non-breaking improvements labels Aug 24, 2026
@IlyasShabi
IlyasShabi marked this pull request as ready for review August 24, 2026 08:46

@szegedi szegedi left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall, I have one small question to better understand it, tho'


size_t extension = state->heap_extension_size;
if (state->automatic_heap_extension) {
if (!state->automatic_heap_extension_size.has_value()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So automatic_heap_extension_size is computed once, and then reused next time. I guess what I'm asking is why is the total_heap_limit - current_heap_limit value computed the first time valid for subsequent times?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diff is the young generation size and It's reusable because the old generation limit is in both terms and cancels, and the young generation is fixed once at isolate startup.

@szegedi szegedi added the semver-minor Usually minor non-breaking improvements label Aug 24, 2026
@szegedi

szegedi commented Aug 24, 2026

Copy link
Copy Markdown

I changed this to "semver-minor" as it introduces new backwards-compatible functionality.

@szegedi szegedi removed the semver-patch Bug or security fixes, mainly label Aug 24, 2026
@IlyasShabi

Copy link
Copy Markdown
Author

@nsavoire Can you please take a look? Asking because I believe you're the author of this

@nsavoire nsavoire left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice improvement !

@szegedi
szegedi merged commit 95835e7 into main Aug 26, 2026
73 of 75 checks passed
@szegedi
szegedi deleted the ishabi/near-oom-callback branch August 26, 2026 08:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

semver-minor Usually minor non-breaking improvements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants