From f190ecf31b9955b57d5eb6a259e5ceaf54f4a477 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Mon, 5 Oct 2026 18:32:18 -0700 Subject: [PATCH] fix(mobile): collect UI runtime garbage on iOS memory warnings React Native only collects the main Hermes runtime when iOS posts a memory warning. The worklets UI runtime is a separate heap that keeps native shadow nodes and synchronizables alive until its own GC runs. Schedule a UI-runtime GC on the same AppState memoryWarning event. Co-Authored-By: Claude Opus 5.5 (1M context) --- apps/mobile/src/App.tsx | 3 +++ .../src/lib/useUiRuntimeMemoryWarningGc.ts | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 apps/mobile/src/lib/useUiRuntimeMemoryWarningGc.ts diff --git a/apps/mobile/src/App.tsx b/apps/mobile/src/App.tsx index f2a809361ee4..86166278108b 100644 --- a/apps/mobile/src/App.tsx +++ b/apps/mobile/src/App.tsx @@ -23,6 +23,7 @@ import { appAtomRegistry } from "./state/atom-registry"; import { OverlayPortalHost } from "./components/OverlayPortal"; import { shouldHandleAppLink } from "./lib/appLinking"; import { useMobileNavigationTheme } from "./lib/useMobileNavigationTheme"; +import { useUiRuntimeMemoryWarningGc } from "./lib/useUiRuntimeMemoryWarningGc"; import { SubscriptionUsageCoordinator } from "./widgets/SubscriptionUsageCoordinator"; import { VoiceInputProvider } from "./features/voice-input/VoiceInputProvider"; import { GlobalVoiceInputControl } from "./features/voice-input/GlobalVoiceInputControl"; @@ -57,6 +58,8 @@ function SplashScreenCoordinator() { } export default function App() { + useUiRuntimeMemoryWarningGc(); + return ( diff --git a/apps/mobile/src/lib/useUiRuntimeMemoryWarningGc.ts b/apps/mobile/src/lib/useUiRuntimeMemoryWarningGc.ts new file mode 100644 index 000000000000..8e702f25aa17 --- /dev/null +++ b/apps/mobile/src/lib/useUiRuntimeMemoryWarningGc.ts @@ -0,0 +1,24 @@ +import { useEffect } from "react"; +import { AppState } from "react-native"; +import { scheduleOnUI } from "react-native-worklets"; + +function collectUiRuntimeGarbage() { + "worklet"; + // Hermes exposes `gc` on the worklets UI runtime; skip quietly on runtimes that do not. + const gc = (globalThis as { gc?: () => void }).gc; + if (typeof gc === "function") gc(); +} + +/** + * React Native only collects the main JS runtime on an iOS memory warning. The + * worklets UI runtime is a separate Hermes heap that holds native shadow nodes and + * serializables alive until its own GC runs, so collect it on the same warning. + */ +export function useUiRuntimeMemoryWarningGc() { + useEffect(() => { + const subscription = AppState.addEventListener("memoryWarning", () => { + scheduleOnUI(collectUiRuntimeGarbage); + }); + return () => subscription.remove(); + }, []); +}