Skip to content

Commit 530e371

Browse files
committed
refactor: explicit invalidations for native and cpp (#6850)
This pull requests refactors memory management of Worklets and Reanimated. Basically, since Reanimated can obtain WorkletsModuleProxy and the Worklet Runtimes as shared pointers, it has to release them explicitly during the invalidation stage of Native Modules. Releasing them later on (e.g. via deconstructors) might lead into issues and crashes. Ideally we'd instead use some different solution here than shared pointers, but it can wait as it's not mandatory at the moment and could be a significant refactor. Fixes: - iOS crash on reload - Android crash on SingleInstanceChecker during third reload - [x] 0.76 iOS/Android Fabric works - [x] 0.76 iOS/Android Paper works - [x] 0.75 iOS/Android Fabric works - [x] 0.75 iOS/Android Paper works - [x] 0.74 iOS/Android Fabric works - [x] 0.74 iOS/Android Paper works
1 parent 77ba526 commit 530e371

File tree

12 files changed

+67
-18
lines changed

12 files changed

+67
-18
lines changed

packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,4 +892,10 @@ void ReanimatedModuleProxy::unsubscribeFromKeyboardEvents(
892892
unsubscribeFromKeyboardEventsFunction_(listenerId.asNumber());
893893
}
894894

895+
void ReanimatedModuleProxy::invalidate() {
896+
// Make sure to release WorkletsModuleProxy on invalidate to allow it
897+
// to destroy its runtime during the invalidation stage.
898+
workletsModuleProxy_.reset();
899+
}
900+
895901
} // namespace reanimated

packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ class ReanimatedModuleProxy : public ReanimatedModuleProxySpec {
5454
jsi::Runtime &rt,
5555
const jsi::Value &workletRuntimeValue,
5656
const jsi::Value &shareableWorkletValue) override;
57+
58+
void invalidate();
5759

5860
jsi::Value registerEventHandler(
5961
jsi::Runtime &rt,
@@ -192,7 +194,7 @@ class ReanimatedModuleProxy : public ReanimatedModuleProxySpec {
192194

193195
const bool isBridgeless_;
194196
const bool isReducedMotion_;
195-
const std::shared_ptr<WorkletsModuleProxy> workletsModuleProxy_;
197+
std::shared_ptr<WorkletsModuleProxy> workletsModuleProxy_;
196198
const std::string valueUnpackerCode_;
197199
std::shared_ptr<WorkletRuntime> uiWorkletRuntime_;
198200

packages/react-native-reanimated/Common/cpp/worklets/Tools/SingleInstanceChecker.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,8 @@ SingleInstanceChecker<T>::SingleInstanceChecker() {
5252
std::string className =
5353
__cxxabiv1::__cxa_demangle(typeid(T).name(), nullptr, nullptr, &status);
5454

55-
// Only one instance should exist, but it is possible for two instances
56-
// to co-exist during a reload.
5755
assertWithMessage(
58-
instanceCount_ <= 1,
56+
instanceCount_ == 0,
5957
"[Reanimated] More than one instance of " + className +
6058
" present. This may indicate a memory leak due to a retain cycle.");
6159

packages/react-native-reanimated/android/src/fabric/java/com/swmansion/reanimated/NativeProxy.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ protected HybridData getHybridData() {
7070
return mHybridData;
7171
}
7272

73+
private native void invalidateCpp();
74+
75+
public void invalidate() {
76+
invalidateCpp();
77+
}
78+
7379
public static NativeMethodsHolder createNativeMethodsHolder(
7480
LayoutAnimations ignoredLayoutAnimations) {
7581
return new NativeMethodsHolder() {

packages/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ void NativeProxy::registerNatives() {
192192
makeNativeMethod(
193193
"isAnyHandlerWaitingForEvent",
194194
NativeProxy::isAnyHandlerWaitingForEvent),
195-
makeNativeMethod("performOperations", NativeProxy::performOperations)});
195+
makeNativeMethod("performOperations", NativeProxy::performOperations),
196+
makeNativeMethod("invalidateCpp", NativeProxy::invalidateCpp)});
196197
}
197198

198199
void NativeProxy::requestRender(std::function<void(double)> onRender) {
@@ -607,4 +608,11 @@ void NativeProxy::setupLayoutAnimations() {
607608
});
608609
}
609610

611+
void NativeProxy::invalidateCpp() {
612+
if (reanimatedModuleProxy_ != nullptr) {
613+
reanimatedModuleProxy_->invalidate();
614+
}
615+
reanimatedModuleProxy_.reset();
616+
}
617+
610618
} // namespace reanimated

packages/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ class NativeProxy : public jni::HybridClass<NativeProxy> {
278278
void commonInit(jni::alias_ref<facebook::react::JFabricUIManager::javaobject>
279279
&fabricUIManager);
280280
#endif // RCT_NEW_ARCH_ENABLED
281+
282+
void invalidateCpp();
281283
};
282284

283285
} // namespace reanimated

packages/react-native-reanimated/android/src/main/cpp/worklets/android/WorkletsModule.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,13 @@ using namespace facebook;
1818
using namespace react;
1919

2020
WorkletsModule::WorkletsModule(
21-
jni::alias_ref<WorkletsModule::javaobject> jThis,
2221
jsi::Runtime *rnRuntime,
2322
const std::string &valueUnpackerCode,
2423
jni::alias_ref<JavaMessageQueueThread::javaobject> messageQueueThread,
2524
const std::shared_ptr<facebook::react::CallInvoker> &jsCallInvoker,
2625
const std::shared_ptr<worklets::JSScheduler> &jsScheduler,
2726
const std::shared_ptr<UIScheduler> &uiScheduler)
28-
: javaPart_(jni::make_global(jThis)),
29-
rnRuntime_(rnRuntime),
27+
: rnRuntime_(rnRuntime),
3028
workletsModuleProxy_(std::make_shared<WorkletsModuleProxy>(
3129
valueUnpackerCode,
3230
std::make_shared<JMessageQueueThread>(messageQueueThread),
@@ -37,7 +35,7 @@ WorkletsModule::WorkletsModule(
3735
}
3836

3937
jni::local_ref<WorkletsModule::jhybriddata> WorkletsModule::initHybrid(
40-
jni::alias_ref<jhybridobject> jThis,
38+
jni::alias_ref<jhybridobject> /*jThis*/,
4139
jlong jsContext,
4240
const std::string &valueUnpackerCode,
4341
jni::alias_ref<JavaMessageQueueThread::javaobject> messageQueueThread,
@@ -51,7 +49,6 @@ jni::local_ref<WorkletsModule::jhybriddata> WorkletsModule::initHybrid(
5149
std::make_shared<worklets::JSScheduler>(*rnRuntime, jsCallInvoker);
5250
auto uiScheduler = androidUIScheduler->cthis()->getUIScheduler();
5351
return makeCxxInstance(
54-
jThis,
5552
rnRuntime,
5653
valueUnpackerCode,
5754
messageQueueThread,
@@ -60,8 +57,14 @@ jni::local_ref<WorkletsModule::jhybriddata> WorkletsModule::initHybrid(
6057
uiScheduler);
6158
}
6259

60+
void WorkletsModule::invalidateCpp() {
61+
workletsModuleProxy_.reset();
62+
}
63+
6364
void WorkletsModule::registerNatives() {
64-
registerHybrid({makeNativeMethod("initHybrid", WorkletsModule::initHybrid)});
65+
registerHybrid(
66+
{makeNativeMethod("initHybrid", WorkletsModule::initHybrid),
67+
makeNativeMethod("invalidateCpp", WorkletsModule::invalidateCpp)});
6568
}
6669

6770
} // namespace worklets

packages/react-native-reanimated/android/src/main/cpp/worklets/android/WorkletsModule.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class WorkletsModule : public jni::HybridClass<WorkletsModule> {
3030
"Lcom/swmansion/worklets/WorkletsModule;";
3131

3232
static jni::local_ref<jhybriddata> initHybrid(
33-
jni::alias_ref<jhybridobject> jThis,
33+
jni::alias_ref<jhybridobject> /*jThis*/,
3434
jlong jsContext,
3535
const std::string &valueUnpackerCode,
3636
jni::alias_ref<JavaMessageQueueThread::javaobject> messageQueueThread,
@@ -46,19 +46,19 @@ class WorkletsModule : public jni::HybridClass<WorkletsModule> {
4646
}
4747

4848
private:
49-
friend HybridBase;
50-
jni::global_ref<WorkletsModule::javaobject> javaPart_;
51-
jsi::Runtime *rnRuntime_;
52-
std::shared_ptr<WorkletsModuleProxy> workletsModuleProxy_;
53-
5449
explicit WorkletsModule(
55-
jni::alias_ref<WorkletsModule::jhybridobject> jThis,
5650
jsi::Runtime *rnRuntime,
5751
const std::string &valueUnpackerCode,
5852
jni::alias_ref<JavaMessageQueueThread::javaobject> messageQueueThread,
5953
const std::shared_ptr<facebook::react::CallInvoker> &jsCallInvoker,
6054
const std::shared_ptr<worklets::JSScheduler> &jsScheduler,
6155
const std::shared_ptr<UIScheduler> &uiScheduler);
56+
57+
void invalidateCpp();
58+
59+
friend HybridBase;
60+
jsi::Runtime *rnRuntime_;
61+
std::shared_ptr<WorkletsModuleProxy> workletsModuleProxy_;
6262
};
6363

6464
} // namespace worklets

packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NodesManager.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ public void invalidate() {
130130
}
131131

132132
if (mNativeProxy != null) {
133+
mNativeProxy.invalidate();
133134
mNativeProxy = null;
134135
}
135136

packages/react-native-reanimated/android/src/main/java/com/swmansion/worklets/WorkletsModule.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ public boolean installTurboModule(String valueUnpackerCode) {
6969
}
7070

7171
public void invalidate() {
72+
// We have to destroy extra runtimes when invalidate is called. If we clean
73+
// it up later instead there's a chance the runtime will retain references
74+
// to invalidated memory and will crash on its destruction.
75+
invalidateCpp();
76+
7277
mAndroidUIScheduler.deactivate();
7378
}
79+
80+
private native void invalidateCpp();
7481
}

0 commit comments

Comments
 (0)