From 56049a0054e89de03f1a30cd4ac9b45d9f85dea6 Mon Sep 17 00:00:00 2001 From: Autopilot Bot Date: Tue, 30 Jun 2026 06:15:49 -0700 Subject: [PATCH] Stop driving the animation backend after Fabric binding teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: WARNING: Generated by Autopilot (alpha) - review carefully, verify the underlying claim before accepting. Agent: React Native Agent | Trajectory: https://www.internalfb.com/intern/devai/devmate/inspector/d5b9a66a-cdcf-42e7-abc9-823520d1bef6/ | SC job: https://www.internalfb.com/intern/sandcastle/instance/18014401277724259/ --- `AnimationBackendChoreographer` registers a self-reposting frame callback with `ReactChoreographer` that keeps invoking `driveAnimationBackend` every frame for the lifetime of the choreographer. `FabricUIManagerBinding.unregister()` tore down the native binding via `uninstallFabricUIManager()` but left that callback running, so a frame that fired after teardown drove a JNI call through the binding's freed native state — a use-after-teardown that can surface as a native crash during React instance reload/teardown while a Fabric (C++ Native Animated) animation is in flight. Pause the choreographer and clear its frame callback in `unregister()` before uninstalling the native binding. A fresh `AnimationBackendChoreographer` is created on every `register()` (it is never reused across instances), and pause/resume are driven by the native animation backend on the next animation, so pausing the now-orphaned choreographer at teardown is safe and does not affect a subsequent re-register. Pausing flips a memory-visible `AtomicBoolean`, so the UI-thread frame callback reliably no-ops even though `unregister()` may run off the UI thread. Changelog: [Android][Fixed] - Stop the Fabric animation frame callback from driving the native binding after teardown, fixing a use-after-teardown native crash Differential Revision: D110150994 --- .../react/fabric/FabricUIManagerBinding.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManagerBinding.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManagerBinding.kt index cad82b4f1b1..6561ca20994 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManagerBinding.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManagerBinding.kt @@ -25,6 +25,8 @@ internal class FabricUIManagerBinding : HybridClassBase() { private external fun initHybrid() + private var animationBackendChoreographer: AnimationBackendChoreographer? = null + private external fun installFabricUIManager( runtimeExecutor: RuntimeExecutor, runtimeScheduler: RuntimeScheduler, @@ -103,6 +105,7 @@ internal class FabricUIManagerBinding : HybridClassBase() { animationBackendChoreographer.frameCallback = AnimationFrameCallback { frameTimeMs: Double -> driveAnimationBackend(frameTimeMs) } + this.animationBackendChoreographer = animationBackendChoreographer setAnimationBackendChoreographer(animationBackendChoreographer) installFabricUIManager( @@ -118,6 +121,19 @@ internal class FabricUIManagerBinding : HybridClassBase() { private external fun uninstallFabricUIManager() fun unregister() { + // Stop driving the animation backend before tearing down the native binding. + // The choreographer's frame callback is registered with ReactChoreographer + // for the choreographer's lifetime and re-posts itself every frame, so it + // keeps invoking driveAnimationBackend even after uninstallFabricUIManager() + // has run. A late frame would then make a JNI call through this binding's + // freed native state (use-after-teardown). Pausing flips a memory-visible + // AtomicBoolean (unregister may run off the UI thread), so the UI-thread + // frame callback reliably no-ops afterwards. + animationBackendChoreographer?.let { choreographer -> + choreographer.pause() + choreographer.frameCallback = null + } + animationBackendChoreographer = null uninstallFabricUIManager() }