diff --git a/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.java b/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.java index f4983edecde1..d56b97001f2c 100644 --- a/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.java +++ b/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.java @@ -27,6 +27,7 @@ import com.swmansion.worklets.WorkletsModule; import java.lang.ref.WeakReference; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; /** * @noinspection JavaJniMissingFunction @@ -47,6 +48,12 @@ public class NativeProxy { private final int ANIMATIONS_DRAG_FACTOR = 10; protected String cppVersion = null; + /** + * Invalidating concurrently could be fatal. It shouldn't happen in a normal flow, but it doesn't + * cost us much to add synchronization for extra safety. + */ + private final AtomicBoolean mInvalidated = new AtomicBoolean(false); + @DoNotStrip @SuppressWarnings("unused") private final HybridData mHybridData; @@ -105,8 +112,13 @@ protected HybridData getHybridData() { return mHybridData; } - public void invalidate() { - invalidateCpp(); + protected void invalidate() { + if (mInvalidated.getAndSet(true)) { + return; + } + if (mHybridData != null && mHybridData.isValid()) { + invalidateCpp(); + } } private void toggleSlowAnimations() { diff --git a/packages/react-native-worklets/android/src/main/java/com/swmansion/worklets/WorkletsModule.java b/packages/react-native-worklets/android/src/main/java/com/swmansion/worklets/WorkletsModule.java index d7480adc7668..82f941e701ad 100644 --- a/packages/react-native-worklets/android/src/main/java/com/swmansion/worklets/WorkletsModule.java +++ b/packages/react-native-worklets/android/src/main/java/com/swmansion/worklets/WorkletsModule.java @@ -14,6 +14,7 @@ import com.swmansion.worklets.runloop.AnimationFrameCallback; import com.swmansion.worklets.runloop.AnimationFrameQueue; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; @SuppressWarnings("JavaJniMissingFunction") @ReactModule(name = WorkletsModule.NAME) @@ -36,6 +37,12 @@ protected HybridData getHybridData() { private final AnimationFrameQueue mAnimationFrameQueue; private boolean mSlowAnimationsEnabled; + /** + * Invalidating concurrently could be fatal. It shouldn't happen in a normal flow, but it doesn't + * cost us much to add synchronization for extra safety. + */ + private final AtomicBoolean mInvalidated = new AtomicBoolean(false); + @OptIn(markerClass = FrameworkAPI.class) private native HybridData initHybrid( long jsContext, @@ -81,11 +88,15 @@ public void toggleSlowAnimations() { } public void invalidate() { - // We have to destroy extra runtimes when invalidate is called. If we clean - // it up later instead there's a chance the runtime will retain references - // to invalidated memory and will crash on its destruction. - invalidateCpp(); - + if (mInvalidated.getAndSet(true)) { + return; + } + if (mHybridData != null && mHybridData.isValid()) { + // We have to destroy extra runtimes when invalidate is called. If we clean + // it up later instead there's a chance the runtime will retain references + // to invalidated memory and will crash on its destruction. + invalidateCpp(); + } mAndroidUIScheduler.deactivate(); }