Skip to content

Commit 3d1afbb

Browse files
JoshuaGrossfacebook-github-bot
authored andcommitted
destroy callbacks even if they aren't called, when java object is destroyed
Summary: JSI callbacks are only destroyed if the callback is called. If the callback is never called, we're potentially leaking a lot of callbacks. To mitigate this, we add a wrapper object that is owned by the std::function. Whenever the std::function is destroyed, the wrapper is destroyed and it deallocates the callback as well. Changelog: [Internal] Reviewed By: RSNara Differential Revision: D27436402 fbshipit-source-id: d153640d5d7988c7fadaf2cb332ec00dadd0689a
1 parent 0901830 commit 3d1afbb

File tree

7 files changed

+95
-32
lines changed

7 files changed

+95
-32
lines changed

ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java

+7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ public class ReactFeatureFlags {
2323
*/
2424
public static volatile boolean useTurboModules = false;
2525

26+
/**
27+
* Should application use the new TM callback manager in Cxx? This is assumed to be a sane
28+
* default, but it's new. We will delete once (1) we know it's safe to ship and (2) we have
29+
* quantified impact.
30+
*/
31+
public static volatile boolean useTurboModulesRAIICallbackManager = false;
32+
2633
/** Should we dispatch TurboModule methods with promise returns to the NativeModules thread? */
2734
public static volatile boolean enableTurboModulePromiseAsyncDispatch = false;
2835

ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/TurboModuleManager.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.facebook.react.bridge.CxxModuleWrapper;
1717
import com.facebook.react.bridge.JSIModule;
1818
import com.facebook.react.bridge.RuntimeExecutor;
19+
import com.facebook.react.config.ReactFeatureFlags;
1920
import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder;
2021
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
2122
import com.facebook.react.turbomodule.core.interfaces.TurboModuleRegistry;
@@ -58,7 +59,8 @@ public TurboModuleManager(
5859
runtimeExecutor,
5960
(CallInvokerHolderImpl) jsCallInvokerHolder,
6061
(CallInvokerHolderImpl) nativeCallInvokerHolder,
61-
delegate);
62+
delegate,
63+
ReactFeatureFlags.useTurboModulesRAIICallbackManager);
6264
installJSIBindings();
6365

6466
mEagerInitModuleNames =
@@ -290,7 +292,8 @@ private native HybridData initHybrid(
290292
RuntimeExecutor runtimeExecutor,
291293
CallInvokerHolderImpl jsCallInvokerHolder,
292294
CallInvokerHolderImpl nativeCallInvokerHolder,
293-
TurboModuleManagerDelegate tmmDelegate);
295+
TurboModuleManagerDelegate tmmDelegate,
296+
boolean useTurboModulesRAIICallbackManager);
294297

295298
private native void installJSIBindings();
296299

ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,15 @@ jni::local_ref<TurboModuleManager::jhybriddata> TurboModuleManager::initHybrid(
3939
jni::alias_ref<JRuntimeExecutor::javaobject> runtimeExecutor,
4040
jni::alias_ref<CallInvokerHolder::javaobject> jsCallInvokerHolder,
4141
jni::alias_ref<CallInvokerHolder::javaobject> nativeCallInvokerHolder,
42-
jni::alias_ref<TurboModuleManagerDelegate::javaobject> delegate) {
42+
jni::alias_ref<TurboModuleManagerDelegate::javaobject> delegate,
43+
bool useTurboModulesRAIICallbackManager) {
4344
auto jsCallInvoker = jsCallInvokerHolder->cthis()->getCallInvoker();
4445
auto nativeCallInvoker = nativeCallInvokerHolder->cthis()->getCallInvoker();
4546

47+
if (useTurboModulesRAIICallbackManager) {
48+
JavaTurboModule::enableUseTurboModulesRAIICallbackManager(true);
49+
}
50+
4651
return makeCxxInstance(
4752
jThis,
4853
runtimeExecutor->cthis()->get(),

ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ class TurboModuleManager : public jni::HybridClass<TurboModuleManager> {
3232
jni::alias_ref<JRuntimeExecutor::javaobject> runtimeExecutor,
3333
jni::alias_ref<CallInvokerHolder::javaobject> jsCallInvokerHolder,
3434
jni::alias_ref<CallInvokerHolder::javaobject> nativeCallInvokerHolder,
35-
jni::alias_ref<TurboModuleManagerDelegate::javaobject> delegate);
35+
jni::alias_ref<TurboModuleManagerDelegate::javaobject> delegate,
36+
bool useTurboModulesRAIICallbackManager);
3637
static void registerNatives();
3738

3839
private:

ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleUtils.h

+18
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,23 @@ class CallbackWrapper : public LongLivedObject {
8484
}
8585
};
8686

87+
class RAIICallbackWrapperDestroyer {
88+
public:
89+
RAIICallbackWrapperDestroyer(std::weak_ptr<CallbackWrapper> callbackWrapper)
90+
: callbackWrapper_(callbackWrapper) {}
91+
92+
~RAIICallbackWrapperDestroyer() {
93+
auto strongWrapper = callbackWrapper_.lock();
94+
if (!strongWrapper) {
95+
return;
96+
}
97+
98+
strongWrapper->destroy();
99+
}
100+
101+
private:
102+
std::weak_ptr<CallbackWrapper> callbackWrapper_;
103+
};
104+
87105
} // namespace react
88106
} // namespace facebook

ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp

+50-28
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ JavaTurboModule::~JavaTurboModule() {
5252
});
5353
}
5454

55+
bool JavaTurboModule::useTurboModulesRAIICallbackManager_ = false;
56+
void JavaTurboModule::enableUseTurboModulesRAIICallbackManager(bool enable) {
57+
JavaTurboModule::useTurboModulesRAIICallbackManager_ = enable;
58+
}
59+
5560
namespace {
5661
jni::local_ref<JCxxCallbackImpl::JavaPart> createJavaCallbackFromJSIFunction(
5762
jsi::Function &&function,
@@ -60,9 +65,20 @@ jni::local_ref<JCxxCallbackImpl::JavaPart> createJavaCallbackFromJSIFunction(
6065
auto weakWrapper =
6166
react::CallbackWrapper::createWeak(std::move(function), rt, jsInvoker);
6267

68+
// This needs to be a shared_ptr because:
69+
// 1. It cannot be unique_ptr. std::function is copyable but unique_ptr is
70+
// not.
71+
// 2. It cannot be weak_ptr since we need this object to live on.
72+
// 3. It cannot be a value, because that would be deleted as soon as this
73+
// function returns.
74+
auto callbackWrapperOwner =
75+
(JavaTurboModule::useTurboModulesRAIICallbackManager_
76+
? std::make_shared<RAIICallbackWrapperDestroyer>(weakWrapper)
77+
: nullptr);
78+
6379
std::function<void(folly::dynamic)> fn =
64-
[weakWrapper,
65-
wrapperWasCalled = false](folly::dynamic responses) mutable {
80+
[weakWrapper, callbackWrapperOwner, wrapperWasCalled = false](
81+
folly::dynamic responses) mutable {
6682
if (wrapperWasCalled) {
6783
throw std::runtime_error(
6884
"callback 2 arg cannot be called more than once");
@@ -73,35 +89,41 @@ jni::local_ref<JCxxCallbackImpl::JavaPart> createJavaCallbackFromJSIFunction(
7389
return;
7490
}
7591

76-
strongWrapper->jsInvoker().invokeAsync([weakWrapper, responses]() {
77-
auto strongWrapper2 = weakWrapper.lock();
78-
if (!strongWrapper2) {
79-
return;
80-
}
81-
82-
// TODO (T43155926) valueFromDynamic already returns a Value array.
83-
// Don't iterate again
84-
jsi::Value args =
85-
jsi::valueFromDynamic(strongWrapper2->runtime(), responses);
86-
auto argsArray = args.getObject(strongWrapper2->runtime())
87-
.asArray(strongWrapper2->runtime());
88-
std::vector<jsi::Value> result;
89-
for (size_t i = 0; i < argsArray.size(strongWrapper2->runtime());
90-
i++) {
91-
result.emplace_back(
92-
strongWrapper2->runtime(),
93-
argsArray.getValueAtIndex(strongWrapper2->runtime(), i));
94-
}
95-
strongWrapper2->callback().call(
96-
strongWrapper2->runtime(),
97-
(const jsi::Value *)result.data(),
98-
result.size());
99-
100-
strongWrapper2->destroy();
101-
});
92+
strongWrapper->jsInvoker().invokeAsync(
93+
[weakWrapper, callbackWrapperOwner, responses]() mutable {
94+
auto strongWrapper2 = weakWrapper.lock();
95+
if (!strongWrapper2) {
96+
return;
97+
}
98+
99+
// TODO (T43155926) valueFromDynamic already returns a Value
100+
// array. Don't iterate again
101+
jsi::Value args =
102+
jsi::valueFromDynamic(strongWrapper2->runtime(), responses);
103+
auto argsArray = args.getObject(strongWrapper2->runtime())
104+
.asArray(strongWrapper2->runtime());
105+
std::vector<jsi::Value> result;
106+
for (size_t i = 0; i < argsArray.size(strongWrapper2->runtime());
107+
i++) {
108+
result.emplace_back(
109+
strongWrapper2->runtime(),
110+
argsArray.getValueAtIndex(strongWrapper2->runtime(), i));
111+
}
112+
strongWrapper2->callback().call(
113+
strongWrapper2->runtime(),
114+
(const jsi::Value *)result.data(),
115+
result.size());
116+
117+
if (JavaTurboModule::useTurboModulesRAIICallbackManager_) {
118+
callbackWrapperOwner.reset();
119+
} else {
120+
strongWrapper2->destroy();
121+
}
122+
});
102123

103124
wrapperWasCalled = true;
104125
};
126+
105127
return JCxxCallbackImpl::newObjectCxxArgs(fn);
106128
}
107129

ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.h

+7
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ class JSI_EXPORT JavaTurboModule : public TurboModule {
5151
const jsi::Value *args,
5252
size_t argCount);
5353

54+
static void enableUseTurboModulesRAIICallbackManager(bool enable);
55+
56+
/**
57+
* Experiments
58+
*/
59+
static bool useTurboModulesRAIICallbackManager_;
60+
5461
private:
5562
jni::global_ref<JTurboModule> instance_;
5663
std::shared_ptr<CallInvoker> nativeInvoker_;

0 commit comments

Comments
 (0)