software-mansion / react-native-reanimated

React Native's Animated library reimplemented
https://docs.swmansion.com/react-native-reanimated/
MIT License
8.67k stars 1.27k forks source link

NodesManager causing an app crash because of the "Map already consumed" #6004

Open ddiachkov opened 2 months ago

ddiachkov commented 2 months ago

Description

We have an app that has a react-navigation entering animation for one of the screens. This screen has a fullscreen animation (Rive) playing in the background. Recently we've moved to the new arch and discovered that if you click on the animation it will crash the app because Reanimated tries to consume an event that was never meant to be consumed by it.

Stack Trace
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI: com.facebook.react.bridge.ObjectAlreadyConsumedException: Map already consumed
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.facebook.react.bridge.NativeMap.toString(Native Method)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.swmansion.reanimated.nativeProxy.EventHandler.receiveEvent(Native Method)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.swmansion.reanimated.nativeProxy.EventHandler.receiveEvent(EventHandler.java:25)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.facebook.react.uimanager.events.Event.dispatch(Event.java:167)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.swmansion.reanimated.NodesManager.handleEvent(NodesManager.java:328)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.swmansion.reanimated.NodesManager.onEventDispatch(NodesManager.java:314)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.facebook.react.uimanager.events.FabricEventDispatcher.dispatchEvent(FabricEventDispatcher.java:43)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.facebook.react.fabric.interop.InteropEventEmitter.receiveEvent(InteropEventEmitter.java:50)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.rivereactnative.RiveReactNativeView.4(RiveReactNativeView.kt:174)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.rivereactnative.RiveReactNativeView$listener$1.notifyPlay(RiveReactNativeView.kt:121)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at app.rive.runtime.kotlin.controllers.RiveFileController.notifyPlay(RiveFileController.kt:780)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at app.rive.runtime.kotlin.controllers.RiveFileController.play$kotlin_release(RiveFileController.kt:652)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at app.rive.runtime.kotlin.controllers.RiveFileController.pointerEvent(RiveFileController.kt:744)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at app.rive.runtime.kotlin.RiveAnimationView.onTouchEvent(RiveAnimationView.kt:978)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.View.dispatchTouchEvent(View.java:14309)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.swmansion.gesturehandler.react.RNGestureHandlerRootView.dispatchTouchEvent(RNGestureHandlerRootView.kt:38)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3118)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2742)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:488)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1871)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.app.Activity.dispatchTouchEvent(Activity.java:4125)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:70)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:446)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.View.dispatchPointerEvent(View.java:14568)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:6016)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:5819)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5310)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5367)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5333)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5485)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5341)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:5542)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5314)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5367)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5333)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5341)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5314)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:8080)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:8031)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7992)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:8203)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:220)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.os.MessageQueue.nativePollOnce(Native Method)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.os.MessageQueue.next(MessageQueue.java:335)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.os.Looper.loop(Looper.java:183)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at android.app.ActivityThread.main(ActivityThread.java:7656)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at java.lang.reflect.Method.invoke(Native Method)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
05-09 11:07:09.875 32364 32364 E MessageQueue-JNI:      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

Root Cause

Reanimated installs custom EventDispatcherListener to listen to animation events, but it fails to validate that the incoming event does belong to the node it animates. That causes double consumption of WritableMap and app crash.

If you look at vanilla NativeAnimatedNodesManager, you can see that it calls event.getEventAnimationDriverMatchSpec() and matchSpec.match(driver.mViewTag, driver.mEventName) to filter the event. NodesManager does something similar, but only when the event is dispatched from non-UI thread.

Potential Fix

I moved the check to the top of the handleEvent:

diff --git a/android/src/main/java/com/swmansion/reanimated/NodesManager.java b/android/src/main/java/com/swmansion/reanimated/NodesManager.java
index b90b215b88009d2b46ea0dfa622faef597e2e1f4..c2445368cd41f032471a9758a91326827ec76fe6 100644
--- a/android/src/main/java/com/swmansion/reanimated/NodesManager.java
+++ b/android/src/main/java/com/swmansion/reanimated/NodesManager.java
@@ -323,18 +323,21 @@ public class NodesManager implements EventDispatcherListener {
     if (mNativeProxy == null) {
       return;
     }
+
+    // Check that we received an event for the node we own
+    String eventName = mCustomEventNamesResolver.resolveCustomEventName(event.getEventName());
+    int viewTag = event.getViewTag();
+    if (!mNativeProxy.isAnyHandlerWaitingForEvent(eventName, viewTag)) {
+      return;
+    }
+
     // Events can be dispatched from any thread so we have to make sure handleEvent is run from the
     // UI thread.
     if (UiThreadUtil.isOnUiThread()) {
       handleEvent(event);
       performOperations();
     } else {
-      String eventName = mCustomEventNamesResolver.resolveCustomEventName(event.getEventName());
-      int viewTag = event.getViewTag();
-      boolean shouldSaveEvent = mNativeProxy.isAnyHandlerWaitingForEvent(eventName, viewTag);
-      if (shouldSaveEvent) {
-        mEventQueue.offer(new CopiedEvent(event));
-      }
+      mEventQueue.offer(new CopiedEvent(event));
       startUpdatingOnAnimationFrame();
     }
   }

This seems to fix the issue. However, I am not 100% sure it won't cause some other bugs.

Steps to reproduce

  1. Create a Screen with some animation: <Stack.Screen options={{ animation: "slide_from_right" }} />
  2. Add an animation to it:
  3. Navigate to the screen and trigger some event, for example, touch the animation.

Snack or a link to a repository

https://github.com/ddiachkov/reanimated-0.73-crash-repro

Reanimated version

3.11.0

React Native version

0.73.5

Platforms

Android

JavaScript runtime

Hermes

Workflow

React Native

Architecture

Fabric (New Architecture)

Build type

Release app & production bundle

Device

Real device

Device model

No response

Acknowledgements

Yes

ddiachkov commented 2 months ago

Upon further investigation, I've found that the bug is not reproducible on 0.74 (or at least not as easy).

I've added minimal repro for 0.73. I think this corner case (mishandling events) is valid.

szydlovsky commented 2 months ago

Hi @ddiachkov! Seems like you got down to the bottom of the problem 😄 . I encourage you to create a PR with your fix proposal - then the team will be able to see themselves whether it risks any bugs or not. When you've created the PR, make sure to link it to this issue