Skip to content

fix(runtime): bound promise tracker to prevent OOM#143

Merged
V3RON merged 4 commits into
callstackincubator:mainfrom
mfazekas:fix/promise-tracker-memory-leak
Jul 3, 2026
Merged

fix(runtime): bound promise tracker to prevent OOM#143
V3RON merged 4 commits into
callstackincubator:mainfrom
mfazekas:fix/promise-tracker-memory-leak

Conversation

@mfazekas

@mfazekas mfazekas commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes an unbounded memory leak in the runtime promise tracker (@react-native-harness/runtime) that could exhaust the JS heap (JavaScript heap out of memory) during long runs.

The tracker recorded every promise created via the global constructor — plus a captured stack — and only released it when the promise settled. Records were never drained between tests/files and had no size bound. Any app that keeps creating promises that are abandoned before they settle (animations, requestAnimationFrame loops, polling, async data-binding) therefore grew the heap linearly with elapsed render time until the process aborted.

The fix:

  • FinalizationRegistry— releases a promise's tracking record as soon as the promise itself is garbage-collected. A collected promise can no longer settle or keep anything pending, so retaining its record (and stack) is pure waste. This is the root-cause fix.
  • Hard size cap (MAX_TRACKED_PROMISES = 10_000, evict-oldest)— a synchronous backstop for when GC can't keep up under heavy allocation pressure. Timeout diagnostics only ever display 10 pending promises, so this is invisible in normal use.

Related Issue

Fixes #142

Context

The tracker only exists to report which promises are still pending when a test times out. It does not need the full session history — only currently-live, unsettled promises. Both changes preserve that behavior: live pending promises stay tracked, while collected/abandoned ones are released and the total is bounded.

No public API changes. The existing clearTrackedPromises() / getPendingPromises() surface is unchanged.

Testing

  • Added promise-tracker-leak.repro.test.ts — a regression guard that fails on the old code (30k records retained, never drained) and passes now (bounded).
  • Added promise-tracker-oom.repro.test.ts — an opt-in (RN_HARNESS_OOM_REPRO=1) reproduction that drives the real tracker under a low heap cap. Before: RSS climbs linearly to FATAL ERROR: Reached heap limit ... JavaScript heap out of memory. After: 50M abandoned promises under --max-old-space-size=512 complete cleanly with RSS pinned at the cap.
  • Full packages/runtime suite compared against a pristine baseline: the leak repro flips fail→pass and there are no new failures (the pre-existing environment-specific failures are unchanged by this PR).

The promise tracker retained a record (with a captured stack) for every
promise created via the global constructor and only released it on settle.
Records were never drained between tests/files and had no size bound, so an
app that continuously creates promises that are abandoned before they settle
(animations, requestAnimationFrame loops, polling, async data-binding) grew
the JS heap linearly until 'JavaScript heap out of memory'.

Release a promise's record as soon as the promise is garbage-collected (via
FinalizationRegistry) and cap the number of retained records as a synchronous
backstop, keeping memory bounded regardless of app bridge/render traffic.

Fixes callstackincubator#142
@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

@mfazekas is attempting to deploy a commit to the Callstack Team on Vercel.

A member of the Team first needs to authorize it.

mfazekas and others added 2 commits July 3, 2026 09:04
Replace runtime reproduction tests with a playground harness regression that verifies abandoned promises are reclaimed after GC.
@V3RON V3RON marked this pull request as ready for review July 3, 2026 08:29
The runtime unit tests continue to cover promise tracker behavior, while the playground harness GC assertion depends on non-deterministic FinalizationRegistry scheduling.
@V3RON V3RON self-requested a review July 3, 2026 10:39

@V3RON V3RON left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you! 🙌

@V3RON V3RON merged commit 367810d into callstackincubator:main Jul 3, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Runtime promise tracker leaks memory (OOM) on continuous promise/bridge traffic

2 participants