Hello, I have a crash using react-native-webgpu with navigation
After repeatedly opening and closing Three.js scenes in browser tabs, I’ve encountered a crash and this error message
HermesGC: OOM: [RNBridgeless] reason = Max heap size was exceeded (1 from category: vm_allocate_category), numCollections = 2296, heapSize = 33554432, allocated = 31563808, va = 33554432, external = 3371365863. LLVM ERROR: OOM: error_code(value = 1, category = vm_allocate_category, message = Max heap size was exceeded)
Each scene has a fixed number of openings depending on its size; I realised this with one scene that crashes after the fifth time, but with another it’s after the twelfth time.
Environment :
"react": "19.1.0",
"react-native": "0.81.5",
"three": "^0.184.0"
"@react-three/fiber": "^9.6.1"
"@react-navigation/native": "^7.1.11",
"@react-navigation/native-stack": "^7.3.16",
Platform: iOS
Device: iPad 3
After extensive research, I realised that the js_externalBytes value returned by HermesInternal.getInstrumentedStats() does not seem to decrease when a scene is closed, which, in my case with a scene that grows to 800 MB, leads to a crash very quickly. However, this issue occurs regardless of the scene’s size, and I have observed the same problem in the example repository here.
Step to reproduce :
- Clone this repository
- Go into FiberCanvas.tsx and replace the content with the code bellow (I only added logs)
- Run the app, go to Three.js > Fiber
- open and close the scene and check the logs to see js_externalBytes increase on every opening
import type { ReconcilerRoot, RootState } from "@react-three/fiber";
import {
createRoot,
events,
extend,
unmountComponentAtNode,
} from "@react-three/fiber";
import React, { useEffect, useRef } from "react";
import type { ViewProps } from "react-native";
import { PixelRatio } from "react-native";
import type { CanvasRef } from "react-native-webgpu";
import { Canvas } from "react-native-webgpu";
import * as THREE from "three";
import { makeWebGPURenderer } from "./makeWebGPURenderer";
//global.THREE = global.THREE || THREE;
interface FiberCanvasProps {
children: React.ReactNode;
style?: ViewProps["style"];
camera?: THREE.PerspectiveCamera;
scene?: THREE.Scene;
}
declare const HermesInternal:
| { getInstrumentedStats?: () => Record<string, number> }
| undefined;
let openCount = 0;
let closeCount = 0;
const logExternalBytes = (label: string) => {
const stats =
typeof HermesInternal !== "undefined"
? HermesInternal?.getInstrumentedStats?.()
: undefined;
console.log(label, {
js_externalBytes: stats?.js_externalBytes,
js_heapSize: stats?.js_heapSize,
});
};
export const FiberCanvas = ({
children,
style,
scene,
camera,
}: FiberCanvasProps) => {
const root = useRef<ReconcilerRoot<OffscreenCanvas>>(null!);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
React.useMemo(() => extend(THREE), []);
const canvasRef = useRef<CanvasRef>(null);
useEffect(() => {
const context = canvasRef.current!.getContext("webgpu")!;
const renderer = makeWebGPURenderer(context);
const canvas = context.canvas as unknown as HTMLCanvasElement;
canvas.width = canvas.clientWidth * PixelRatio.get();
canvas.height = canvas.clientHeight * PixelRatio.get();
const size = {
top: 0,
left: 0,
width: canvas.clientWidth,
height: canvas.clientHeight,
};
if (!root.current) {
root.current = createRoot(canvas);
}
root.current.configure({
size,
events,
scene,
camera,
gl: renderer,
frameloop: "always",
dpr: 1, //PixelRatio.get(),
onCreated: async (state: RootState) => {
await state.gl.init();
const renderFrame = state.gl.render.bind(state.gl);
state.gl.render = (s: THREE.Scene, c: THREE.Camera) => {
renderFrame(s, c);
context.present();
};
openCount += 1;
logExternalBytes(`[open #${openCount}] scene opened`);
},
});
root.current.render(children);
return () => {
closeCount += 1;
logExternalBytes(`[close #${closeCount}] scene start`);
if (canvas != null) {
unmountComponentAtNode(canvas!);
}
};
});
return <Canvas ref={canvasRef} style={style} />;
};
(NOBRIDGE) LOG [open #1] scene opened {"js_externalBytes": 11589887, "js_heapSize": 20971520}
(NOBRIDGE) LOG [close #1] scene start {"js_externalBytes": 85933905, "js_heapSize": 25165824}
(NOBRIDGE) LOG [open #2] scene opened {"js_externalBytes": 86326305, "js_heapSize": 25165824}
(NOBRIDGE) LOG [close #2] scene start {"js_externalBytes": 159344810, "js_heapSize": 25165824}
(NOBRIDGE) LOG [open #3] scene opened {"js_externalBytes": 159896250, "js_heapSize": 25165824}
(NOBRIDGE) LOG [close #3] scene start {"js_externalBytes": 233057918, "js_heapSize": 25165824}
(NOBRIDGE) LOG [open #4] scene opened {"js_externalBytes": 233463710, "js_heapSize": 25165824}
(NOBRIDGE) LOG [close #4] scene start {"js_externalBytes": 307577342, "js_heapSize": 29360128}
...
For the example I tried to do the same on the Helmet :
(NOBRIDGE) LOG [open #1] scene opened {"js_externalBytes": 100241837, "js_heapSize": 16777216}
(NOBRIDGE) LOG [close #1] scene start {"js_externalBytes": 325081896, "js_heapSize": 20971520}
(NOBRIDGE) LOG [open #2] scene opened {"js_externalBytes": 413379661, "js_heapSize": 20971520}
(NOBRIDGE) LOG [close #2] scene start {"js_externalBytes": 638207944, "js_heapSize": 20971520}
(NOBRIDGE) LOG [open #3] scene opened {"js_externalBytes": 738823875, "js_heapSize": 20971520}
(NOBRIDGE) LOG [close #3] scene start {"js_externalBytes": 951805688, "js_heapSize": 25165824}
...
(NOBRIDGE) LOG [open #8] scene opened {"js_externalBytes": 2323505937, "js_heapSize": 25165824}
Unfortunately, I can’t go any further to look at the OOM because of another memory issue.
So I just added a "renderer.dispose();" at the end of the useEffect in Helmet.tsx to clean memory on each destroy.
And finally I got the crash on the
'[open #12] scene opened', { js_externalBytes: 3316571485, js_heapSize: 29360128 }
HermesGC: OOM: [RNBridgeless] reason = Max heap size was exceeded (1 from category: vm_allocate_category), numCollections = 2343, heapSize = 16777216, allocated = 15835072, va = 16777216, external = 3423283937.
LLVM ERROR: OOM: error_code(value = 1, category = vm_allocate_category, message = Max heap size was exceeded)
I hope this will help to understand and resolve the problem; thank you in advance for your help
Hello, I have a crash using react-native-webgpu with navigation
After repeatedly opening and closing Three.js scenes in browser tabs, I’ve encountered a crash and this error message
HermesGC: OOM: [RNBridgeless] reason = Max heap size was exceeded (1 from category: vm_allocate_category), numCollections = 2296, heapSize = 33554432, allocated = 31563808, va = 33554432, external = 3371365863. LLVM ERROR: OOM: error_code(value = 1, category = vm_allocate_category, message = Max heap size was exceeded)Each scene has a fixed number of openings depending on its size; I realised this with one scene that crashes after the fifth time, but with another it’s after the twelfth time.
Environment :
"react": "19.1.0",
"react-native": "0.81.5",
"three": "^0.184.0"
"@react-three/fiber": "^9.6.1"
"@react-navigation/native": "^7.1.11",
"@react-navigation/native-stack": "^7.3.16",
Platform: iOS
Device: iPad 3
After extensive research, I realised that the
js_externalBytesvalue returned byHermesInternal.getInstrumentedStats()does not seem to decrease when a scene is closed, which, in my case with a scene that grows to 800 MB, leads to a crash very quickly. However, this issue occurs regardless of the scene’s size, and I have observed the same problem in the example repository here.Step to reproduce :
For the example I tried to do the same on the Helmet :
I hope this will help to understand and resolve the problem; thank you in advance for your help