From 86a388dfb21bc35559711e39f4be9cfb322efc1d Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 20 Jul 2026 11:49:35 +0300 Subject: [PATCH 1/2] =?UTF-8?q?chore(=F0=9F=90=99):=20add=20runtime=20relo?= =?UTF-8?q?ad=20lifecycle=20diagnostic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/example/src/App.tsx | 2 + .../src/Diagnostics/ReloadLifecycle.tsx | 139 ++++++++++++++++++ apps/example/src/Home.tsx | 4 + apps/example/src/Route.ts | 1 + 4 files changed, 146 insertions(+) create mode 100644 apps/example/src/Diagnostics/ReloadLifecycle.tsx diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx index cc85c03b5..08e0b2838 100644 --- a/apps/example/src/App.tsx +++ b/apps/example/src/App.tsx @@ -37,6 +37,7 @@ import { AsyncStarvation } from "./Diagnostics/AsyncStarvation"; import { DeviceLostHang } from "./Diagnostics/DeviceLostHang"; import { DiagnosticsList } from "./Diagnostics/DiagnosticsList"; import { WorkletRequestAdapter } from "./Diagnostics/WorkletRequestAdapter"; +import { ReloadLifecycle } from "./Diagnostics/ReloadLifecycle"; import { ContextEdgeCases } from "./Diagnostics/ContextEdgeCases"; import { ViewFormatsUseAfterFree } from "./Diagnostics/ViewFormatsUseAfterFree"; import { RenderAfterUnmount } from "./Diagnostics/RenderAfterUnmount"; @@ -113,6 +114,7 @@ function App() { name="WorkletRequestAdapter" component={WorkletRequestAdapter} /> + + device.createComputePipelineAsync({ + label: "reload-lifecycle-pending-pipeline", + layout: "auto", + compute: { + module: device.createShaderModule({ + code: "@compute @workgroup_size(1) fn main() {}", + }), + entryPoint: "main", + }, + }); + +export const ReloadLifecycle = () => { + const deviceRef = useRef(null); + const [status, setStatus] = useState("Requesting adapter…"); + const [error, setError] = useState(null); + + useEffect(() => { + let cancelled = false; + + const prepare = async () => { + try { + const adapter = await navigator.gpu.requestAdapter(); + if (!adapter) { + throw new Error("Failed to acquire a GPU adapter."); + } + + const device = await adapter.requestDevice({ + label: "reload-lifecycle-probe", + }); + if (cancelled) { + return; + } + + deviceRef.current = device; + setStatus("WebGPU is ready. Reload can now be triggered."); + + // Keep a native callback registered so runtime teardown has to cancel it. + void device.lost.catch(() => undefined); + } catch (cause) { + if (!cancelled) { + setError(cause instanceof Error ? cause.message : String(cause)); + } + } + }; + + void prepare(); + return () => { + cancelled = true; + deviceRef.current = null; + }; + }, []); + + const reload = () => { + const device = deviceRef.current; + if (!device) { + return; + } + + // Do not await these operations: reload must invalidate their callbacks while + // they are still associated with the current JavaScript runtime. + void createPendingPipeline(device).catch(() => undefined); + void device.queue.onSubmittedWorkDone().catch(() => undefined); + DevSettings.reload("react-native-webgpu lifecycle regression"); + }; + + const ready = deviceRef.current !== null; + + return ( + + + Runtime Reload Lifecycle + {status} + {error ? {error} : null} + + Start async work and reload + + + After reload, open this screen again. A healthy installation acquires + a fresh adapter and device and reaches the ready state. + + + + ); +}; diff --git a/apps/example/src/Home.tsx b/apps/example/src/Home.tsx index fd72751ad..9587f5a0f 100644 --- a/apps/example/src/Home.tsx +++ b/apps/example/src/Home.tsx @@ -119,6 +119,10 @@ export const examples = [ screen: "Diagnostics", title: "⚠️ Tests", }, + { + screen: "ReloadLifecycle", + title: "♻️ Runtime Reload Lifecycle", + }, { screen: "StorageBufferVertices", title: "💾 Storage Buffer Vertices", diff --git a/apps/example/src/Route.ts b/apps/example/src/Route.ts index a3c4cea20..329465bf4 100644 --- a/apps/example/src/Route.ts +++ b/apps/example/src/Route.ts @@ -30,6 +30,7 @@ export type Routes = { AsyncStarvation: undefined; DeviceLostHang: undefined; WorkletRequestAdapter: undefined; + ReloadLifecycle: undefined; ContextEdgeCases: undefined; ViewFormatsUseAfterFree: undefined; RenderAfterUnmount: undefined; From a544bdb4b91ab69a2e34069bd1a8739e0f57e979 Mon Sep 17 00:00:00 2001 From: Nikita Dudin Date: Mon, 20 Jul 2026 12:37:19 +0300 Subject: [PATCH 2/2] =?UTF-8?q?chore(=F0=9F=90=99):=20move=20reload=20diag?= =?UTF-8?q?nostic=20into=20tests=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/example/src/Diagnostics/DiagnosticsList.tsx | 4 ++++ apps/example/src/Home.tsx | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/example/src/Diagnostics/DiagnosticsList.tsx b/apps/example/src/Diagnostics/DiagnosticsList.tsx index 64d0a2ca5..3b514ffdc 100644 --- a/apps/example/src/Diagnostics/DiagnosticsList.tsx +++ b/apps/example/src/Diagnostics/DiagnosticsList.tsx @@ -21,6 +21,10 @@ const tests = [ screen: "WorkletRequestAdapter", title: "⚠️ Worklet requestAdapter", }, + { + screen: "ReloadLifecycle", + title: "⚠️ Runtime Reload Lifecycle", + }, { screen: "ContextEdgeCases", title: "⚠️ Context Edge Cases", diff --git a/apps/example/src/Home.tsx b/apps/example/src/Home.tsx index 9587f5a0f..fd72751ad 100644 --- a/apps/example/src/Home.tsx +++ b/apps/example/src/Home.tsx @@ -119,10 +119,6 @@ export const examples = [ screen: "Diagnostics", title: "⚠️ Tests", }, - { - screen: "ReloadLifecycle", - title: "♻️ Runtime Reload Lifecycle", - }, { screen: "StorageBufferVertices", title: "💾 Storage Buffer Vertices",