From 822331c36a9571684b7f869d18fe0b791544a631 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Mon, 20 Apr 2026 09:39:16 -0700 Subject: [PATCH] Evict Metro bundle graphs after each Fantom test to prevent OOM Summary: Changelog: [Internal] Differential Revision: D101652820 --- .../react-native-fantom/runner/bundling.js | 13 +++++++++- .../runner/global-setup/globalTeardown.js | 25 ++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/private/react-native-fantom/runner/bundling.js b/private/react-native-fantom/runner/bundling.js index de334ec28ccd..ddaf839581d2 100644 --- a/private/react-native-fantom/runner/bundling.js +++ b/private/react-native-fantom/runner/bundling.js @@ -28,6 +28,8 @@ export async function createBundle(options: BundleOptions): Promise { let lastBundleResult; let lastBundleError; + const bundleURL = getBundleURL(options); + // Retry in case Metro hasn't seen the changes in the filesystem yet. // TODO(T231910841): Remove this when Metro fixes consistency issues when resolving HTTP requests. let attemps = 0; @@ -40,7 +42,7 @@ export async function createBundle(options: BundleOptions): Promise { lastBundleResult = null; try { - lastBundleResult = await fetch(getBundleURL(options)); + lastBundleResult = await fetch(bundleURL); } catch (e) { lastBundleError = e; } @@ -72,6 +74,15 @@ export async function createBundle(options: BundleOptions): Promise { await lastBundleResult.text(), 'utf8', ); + + // Each test uses a unique entrypoint, so the bundle graph will never be + // requested again. Send DELETE to evict Metro's cached dependency graph + // and delta calculator for this bundle, freeing the memory. + try { + await fetch(bundleURL, {method: 'DELETE'}); + } catch { + // Best-effort cleanup — don't fail the test if eviction fails. + } } export async function createSourceMap(options: BundleOptions): Promise { diff --git a/private/react-native-fantom/runner/global-setup/globalTeardown.js b/private/react-native-fantom/runner/global-setup/globalTeardown.js index 312539bcbcfc..566f137677c3 100644 --- a/private/react-native-fantom/runner/global-setup/globalTeardown.js +++ b/private/react-native-fantom/runner/global-setup/globalTeardown.js @@ -14,6 +14,8 @@ type MetroServer = NonNullable; declare var __FANTOM_METRO_SERVER__: ?RunServerResult; +const SHUTDOWN_TIMEOUT_MS = 5_000; + function getMetroServer(): ?MetroServer { return typeof __FANTOM_METRO_SERVER__ !== 'undefined' && __FANTOM_METRO_SERVER__ != null @@ -32,7 +34,11 @@ export default async function globalTeardown( } async function stopMetroServer(metroServer: MetroServer): Promise { - return new Promise((resolve, reject) => { + if (!metroServer.listening) { + return; + } + + const closed = new Promise((resolve, reject) => { metroServer.close(error => { if (error) { reject(error); @@ -41,4 +47,21 @@ async function stopMetroServer(metroServer: MetroServer): Promise { } }); }); + + // $FlowFixMe[method-unbinding] Node 18.2+ API + if (typeof metroServer.closeIdleConnections === 'function') { + metroServer.closeIdleConnections(); + } + + const timeout = new Promise(resolve => { + setTimeout(() => { + // $FlowFixMe[method-unbinding] Node 18.2+ API + if (typeof metroServer.closeAllConnections === 'function') { + metroServer.closeAllConnections(); + } + resolve(); + }, SHUTDOWN_TIMEOUT_MS); + }); + + await Promise.race([closed, timeout]); }