Skip to content

Commit 325a644

Browse files
authored
fix(Worklets): tie RNR remote function lifetime to TurboModule invalidate status (#9789)
## Summary Fixes #9751 Changing (again) how RN Runtime remote functions (RFs) are managed, since changes from #9673 and #9272 were actually insufficient and the map approach won't work. It's impossible to keep both the actual callback and cached serialized callback value alive without a memory cycle, so I'm dropping cache consistency. This might result in multiple serializations of a given RF the GC triggered and the serialized value wasn't saved, but the serialization of a RF is actually cheap. I'm dropping the map approach entirely in favor of the behavior where destructor invocation is based upon the information if the runtime is still alive. We can't directly check it, so I'm depending on the `invalidate()` method of Turbo Modules - which is supposed to be called when the runtime is still alive. ## Test plan You can test it on [df3e344](df3e344) commit - there's a special registry there that artificially extends the lifetime of Remote Functions - click some buttons on the provided example, then reload the app with R button. The callbacks with persist and will crash on destructor invocation without the check for `isDead`.
1 parent baf8d71 commit 325a644

22 files changed

Lines changed: 175 additions & 265 deletions

packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -335,25 +335,22 @@ jsi::Object JSIWorkletsModuleProxy::toOptimizedObject(jsi::Runtime &rt) const {
335335
return makeSerializableArrayBuffer(rt, buffer);
336336
});
337337

338-
jsi_utils::addMethod<3>(
338+
jsi_utils::addMethod<2>(
339339
rt,
340340
obj,
341341
"createSerializableNonWorkletFunction",
342-
[jsScheduler = jsScheduler_, hostRuntimeId = hostRuntimeId_](
343-
jsi::Runtime &rt, const jsi::Value &, const jsi::Value(&args)[3]) {
342+
[jsScheduler = jsScheduler_, hostRuntimeId = hostRuntimeId_, rnRuntimeStatus = rnRuntimeStatus_](
343+
jsi::Runtime &rt, const jsi::Value &, const jsi::Value(&args)[2]) {
344344
auto fun = at<0>(args).getObject(rt).getFunction(rt);
345-
const auto name = at<2>(args).isUndefined() ? "" : at<2>(args).getString(rt).utf8(rt);
345+
const auto name = at<1>(args).isUndefined() ? "" : at<1>(args).getString(rt).utf8(rt);
346346
if (fun.isHostFunction(rt)) {
347347
return makeSerializableHostFunction(
348348
rt, fun.getHostFunction(rt), name, fun.getProperty(rt, "length").getNumber());
349+
} else if (hostRuntimeId == RuntimeData::rnRuntimeId) {
350+
return makeRNRuntimeSerializableRemoteFunction(rt, name, fun, jsScheduler, rnRuntimeStatus);
351+
} else {
352+
return makeWorkletRuntimeSerializableRemoteFunction(rt, name, fun, hostRuntimeId);
349353
}
350-
if (hostRuntimeId == RuntimeData::rnRuntimeId) {
351-
const int remoteId = static_cast<int>(at<1>(args).getNumber());
352-
auto ref = makeRNOriginSerializableRemoteFunction(rt, name, remoteId, jsScheduler);
353-
ref.asObject(rt).setProperty(rt, "__keepAlive", true);
354-
return ref;
355-
}
356-
return makeWorkletOriginSerializableRemoteFunction(rt, name, std::move(fun), hostRuntimeId);
357354
});
358355

359356
jsi_utils::addMethod<2>(

packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <worklets/SharedItems/MemoryManager.h>
55
#include <worklets/SharedItems/Serializable.h>
66
#include <worklets/SharedItems/UnpackerLoader.h>
7+
#include <worklets/Tools/RNRuntimeStatus.h>
78
#include <worklets/Tools/ScriptBuffer.h>
89
#include <worklets/WorkletRuntime/BundleModeConfig.h>
910
#include <worklets/WorkletRuntime/RuntimeBindings.h>
@@ -31,6 +32,7 @@ class JSIWorkletsModuleProxy : public std::enable_shared_from_this<JSIWorkletsMo
3132
const std::shared_ptr<RuntimeBindings> &runtimeBindings,
3233
const BundleModeConfig &bundleModeConfig,
3334
const std::shared_ptr<UnpackerLoader> &unpackerLoader,
35+
const std::shared_ptr<RNRuntimeStatus> &rnRuntimeStatus,
3436
RuntimeData::RuntimeId hostRuntimeId)
3537
: isDevBundle_(isDevBundle),
3638
bundleModeConfig_(bundleModeConfig),
@@ -41,6 +43,7 @@ class JSIWorkletsModuleProxy : public std::enable_shared_from_this<JSIWorkletsMo
4143
uiWorkletRuntime_(uiWorkletRuntime),
4244
runtimeBindings_(runtimeBindings),
4345
unpackerLoader_(unpackerLoader),
46+
rnRuntimeStatus_(rnRuntimeStatus),
4447
hostRuntimeId_(hostRuntimeId) {}
4548

4649
static std::shared_ptr<JSIWorkletsModuleProxy> createForNewRuntime(
@@ -56,6 +59,7 @@ class JSIWorkletsModuleProxy : public std::enable_shared_from_this<JSIWorkletsMo
5659
sourceProxy->runtimeBindings_,
5760
sourceProxy->bundleModeConfig_,
5861
sourceProxy->unpackerLoader_,
62+
sourceProxy->rnRuntimeStatus_,
5963
hostRuntimeId);
6064
}
6165

@@ -115,6 +119,7 @@ class JSIWorkletsModuleProxy : public std::enable_shared_from_this<JSIWorkletsMo
115119
const std::weak_ptr<WorkletRuntime> uiWorkletRuntime_;
116120
const std::shared_ptr<RuntimeBindings> runtimeBindings_;
117121
const std::shared_ptr<UnpackerLoader> unpackerLoader_;
122+
const std::shared_ptr<RNRuntimeStatus> rnRuntimeStatus_;
118123
const RuntimeData::RuntimeId hostRuntimeId_;
119124
};
120125

packages/react-native-worklets/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ WorkletsModuleProxy::WorkletsModuleProxy(
4545
const std::shared_ptr<UIScheduler> &uiScheduler,
4646
std::function<bool()> &&isJavaScriptThread,
4747
const std::shared_ptr<RuntimeBindings> &runtimeBindings,
48-
const BundleModeConfig &bundleModeConfig)
48+
const BundleModeConfig &bundleModeConfig,
49+
const std::shared_ptr<RNRuntimeStatus> &rnRuntimeStatus)
4950
: isDevBundle_(isDevBundleFromRNRuntime(rnRuntime)),
5051
jsScheduler_(std::make_shared<JSScheduler>(rnRuntime, jsCallInvoker, std::move(isJavaScriptThread))),
5152
uiScheduler_(uiScheduler),
@@ -55,6 +56,7 @@ WorkletsModuleProxy::WorkletsModuleProxy(
5556
memoryManager_(std::make_shared<MemoryManager>()),
5657
runtimeManager_(std::make_shared<RuntimeManager>()),
5758
unpackerLoader_(std::make_shared<UnpackerLoader>()),
59+
rnRuntimeStatus_(rnRuntimeStatus),
5860
uiWorkletRuntime_(runtimeManager_->createUninitializedUIRuntime(std::make_shared<AsyncQueueUI>(uiScheduler_))),
5961
rnRuntimeProxy_(std::make_shared<JSIWorkletsModuleProxy>(
6062
isDevBundle_,
@@ -66,6 +68,7 @@ WorkletsModuleProxy::WorkletsModuleProxy(
6668
runtimeBindings_,
6769
bundleModeConfig_,
6870
unpackerLoader_,
71+
rnRuntimeStatus_,
6972
RuntimeData::rnRuntimeId)) {
7073
RNRuntimeWorkletDecorator::decorate(rnRuntime, rnRuntimeProxy_->toOptimizedObject(rnRuntime), jsLogger_);
7174
}

packages/react-native-worklets/Common/cpp/worklets/NativeModules/WorkletsModuleProxy.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <worklets/SharedItems/UnpackerLoader.h>
88
#include <worklets/Tools/JSLogger.h>
99
#include <worklets/Tools/JSScheduler.h>
10+
#include <worklets/Tools/RNRuntimeStatus.h>
1011
#include <worklets/Tools/ScriptBuffer.h>
1112
#include <worklets/Tools/SingleInstanceChecker.h>
1213
#include <worklets/Tools/UIScheduler.h>
@@ -28,7 +29,8 @@ class WorkletsModuleProxy : public std::enable_shared_from_this<WorkletsModulePr
2829
const std::shared_ptr<UIScheduler> &uiScheduler,
2930
std::function<bool()> &&isJavaScriptQueue,
3031
const std::shared_ptr<RuntimeBindings> &runtimeBindings,
31-
const BundleModeConfig &bundleModeConfig);
32+
const BundleModeConfig &bundleModeConfig,
33+
const std::shared_ptr<RNRuntimeStatus> &rnRuntimeStatus);
3234

3335
~WorkletsModuleProxy();
3436

@@ -62,6 +64,7 @@ class WorkletsModuleProxy : public std::enable_shared_from_this<WorkletsModulePr
6264
const std::shared_ptr<MemoryManager> memoryManager_;
6365
const std::shared_ptr<RuntimeManager> runtimeManager_;
6466
const std::shared_ptr<UnpackerLoader> unpackerLoader_;
67+
const std::shared_ptr<RNRuntimeStatus> rnRuntimeStatus_;
6568
std::shared_ptr<WorkletRuntime> uiWorkletRuntime_;
6669
const std::shared_ptr<JSIWorkletsModuleProxy> rnRuntimeProxy_;
6770
std::shared_ptr<AnimationFrameBatchinator> animationFrameBatchinator_;

packages/react-native-worklets/Common/cpp/worklets/SharedItems/Serializable.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include <optional>
1212
#include <string>
1313
#include <utility>
14-
#include <variant>
1514
#include <vector>
1615

1716
using namespace facebook;

packages/react-native-worklets/Common/cpp/worklets/SharedItems/SerializableFactory.cpp

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,22 +61,31 @@ jsi::Value makeSerializableHostFunction(
6161
return SerializableJSRef::newNativeStateObject(rt, serializable);
6262
}
6363

64-
jsi::Value makeRNOriginSerializableRemoteFunction(
64+
jsi::Value makeRNRuntimeSerializableRemoteFunction(
6565
jsi::Runtime &rnRuntime,
6666
const std::string &name,
67-
const int remoteId,
68-
const std::shared_ptr<JSScheduler> &jsScheduler) {
69-
auto serializable = std::make_shared<SerializableRemoteFunction::RNOrigin>(rnRuntime, name, remoteId, jsScheduler);
67+
const jsi::Function &function,
68+
const std::shared_ptr<JSScheduler> &jsScheduler,
69+
const std::shared_ptr<RNRuntimeStatus> &rnRuntimeStatus) {
70+
auto serializable = std::make_shared<SerializableRemoteFunction>(
71+
rnRuntime,
72+
name,
73+
jsi::Value(rnRuntime, function).getObject(rnRuntime).getFunction(rnRuntime),
74+
jsScheduler,
75+
rnRuntimeStatus);
7076
return SerializableJSRef::newNativeStateObject(rnRuntime, serializable);
7177
}
7278

73-
jsi::Value makeWorkletOriginSerializableRemoteFunction(
79+
jsi::Value makeWorkletRuntimeSerializableRemoteFunction(
7480
jsi::Runtime &workletRuntime,
7581
const std::string &name,
76-
jsi::Function &&function,
82+
const jsi::Function &function,
7783
RuntimeData::RuntimeId hostRuntimeId) {
78-
auto serializable = std::make_shared<SerializableRemoteFunction::WorkletOrigin>(
79-
workletRuntime, name, std::move(function), hostRuntimeId);
84+
auto serializable = std::make_shared<SerializableRemoteFunction>(
85+
workletRuntime,
86+
name,
87+
jsi::Value(workletRuntime, function).getObject(workletRuntime).getFunction(workletRuntime),
88+
hostRuntimeId);
8089
return SerializableJSRef::newNativeStateObject(workletRuntime, serializable);
8190
}
8291

packages/react-native-worklets/Common/cpp/worklets/SharedItems/SerializableFactory.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
namespace worklets {
1111

12+
class RNRuntimeStatus;
13+
1214
jsi::Value makeSerializableString(jsi::Runtime &rt, const jsi::String &string);
1315

1416
jsi::Value makeSerializableNumber(jsi::Runtime &rt, double number);
@@ -63,16 +65,17 @@ jsi::Value makeSerializableHostFunction(
6365
const std::string &name,
6466
unsigned int paramCount);
6567

66-
jsi::Value makeRNOriginSerializableRemoteFunction(
68+
jsi::Value makeRNRuntimeSerializableRemoteFunction(
6769
jsi::Runtime &rnRuntime,
6870
const std::string &name,
69-
int remoteId,
70-
const std::shared_ptr<JSScheduler> &jsScheduler);
71+
const jsi::Function &function,
72+
const std::shared_ptr<JSScheduler> &jsScheduler,
73+
const std::shared_ptr<RNRuntimeStatus> &rnRuntimeStatus);
7174

72-
jsi::Value makeWorkletOriginSerializableRemoteFunction(
75+
jsi::Value makeWorkletRuntimeSerializableRemoteFunction(
7376
jsi::Runtime &workletRuntime,
7477
const std::string &name,
75-
jsi::Function &&function,
78+
const jsi::Function &function,
7679
RuntimeData::RuntimeId hostRuntimeId);
7780

7881
jsi::Value makeSerializableWorklet(jsi::Runtime &rt, const jsi::Object &object, const bool &shouldRetainRemote);

packages/react-native-worklets/Common/cpp/worklets/SharedItems/SerializableRemoteFunction.cpp

Lines changed: 31 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
#include <worklets/WorkletRuntime/WorkletRuntime.h>
77

88
#include <memory>
9-
#include <utility>
109

1110
using namespace facebook;
1211

@@ -20,94 +19,51 @@ jsi::Function getRemoteFunctionUnpacker(jsi::Runtime &rt) {
2019
return remoteFunctionUnpacker.asObject(rt).asFunction(rt);
2120
}
2221

23-
jsi::Object getRemoteFunctionRegistry(jsi::Runtime &rt) {
24-
auto registry = rt.global().getProperty(rt, "__remoteFunctionRegistry");
25-
react_native_assert(registry.isObject() && "remoteFunctionRegistry not found");
26-
return registry.getObject(rt);
27-
}
28-
2922
} // namespace
3023

31-
SerializableRemoteFunction::~SerializableRemoteFunction() = default;
32-
33-
jsi::Value SerializableRemoteFunction::unpackSelf(jsi::Runtime &rt) {
34-
const auto nameValue = name_.empty() ? jsi::Value::undefined() : jsi::String::createFromUtf8(rt, name_);
35-
auto holderFunction = getRemoteFunctionUnpacker(rt).call(rt, nameValue).getObject(rt);
36-
holderFunction.setNativeState(rt, std::make_shared<SerializableJSRef>(shared_from_this()));
37-
return holderFunction;
38-
}
39-
40-
jsi::Value SerializableRemoteFunction::RNOrigin::toJSValue(jsi::Runtime &rt) {
41-
if (&rt == hostRuntime_) {
42-
const auto registry = getRemoteFunctionRegistry(rt);
43-
return registry.getPropertyAsFunction(rt, "get").callWithThis(rt, registry, jsi::Value(remoteId_));
44-
}
45-
std::shared_ptr<SerializableRemoteFunction> proxy;
46-
{
47-
std::lock_guard<std::mutex> lock(proxyMutex_);
48-
proxy = proxy_.lock();
49-
if (!proxy) {
50-
auto self = std::static_pointer_cast<RNOrigin>(shared_from_this());
51-
proxy = std::make_shared<RNOriginProxy>(self);
52-
proxy_ = proxy;
24+
SerializableRemoteFunction::~SerializableRemoteFunction() {
25+
if (isHostedOnRNRuntime()) {
26+
if (rnRuntimeStatus_->isDead()) {
27+
freeWithoutCallingDestructor(function_);
28+
} else {
29+
function_.reset();
5330
}
31+
} else {
32+
cleanupRuntimeAware(hostRuntime_, function_);
5433
}
55-
return proxy->toJSValue(rt);
56-
}
57-
58-
void SerializableRemoteFunction::RNOrigin::resolveOrRejectPromise(
59-
const std::shared_ptr<Serializable> &resolveValue,
60-
const std::shared_ptr<RuntimeManager> & /*runtimeManager*/) {
61-
jsScheduler_->scheduleOnJS([resolver = shared_from_this(), resolveValue](jsi::Runtime &rt) {
62-
resolver->toJSValue(rt).getObject(rt).getFunction(rt).call(rt, resolveValue->toJSValue(rt));
63-
});
64-
}
65-
66-
SerializableRemoteFunction::WorkletOrigin::~WorkletOrigin() {
67-
cleanupRuntimeAware(hostRuntime_, function_);
6834
}
6935

70-
jsi::Value SerializableRemoteFunction::WorkletOrigin::toJSValue(jsi::Runtime &rt) {
36+
jsi::Value SerializableRemoteFunction::toJSValue(jsi::Runtime &rt) {
7137
if (&rt == hostRuntime_) {
7238
return jsi::Value(rt, *function_);
39+
} else {
40+
const auto name = name_.empty() ? jsi::Value::undefined() : jsi::String::createFromUtf8(rt, name_);
41+
auto holderFunction = getRemoteFunctionUnpacker(rt).call(rt, name).getObject(rt);
42+
holderFunction.setNativeState(rt, std::make_shared<SerializableJSRef>(shared_from_this()));
43+
return holderFunction;
7344
}
74-
return unpackSelf(rt);
7545
}
7646

77-
void SerializableRemoteFunction::WorkletOrigin::resolveOrRejectPromise(
47+
// TODO: generalize it and merge with other scheduling methods
48+
void SerializableRemoteFunction::resolveOrRejectPromise(
7849
const std::shared_ptr<Serializable> &resolveValue,
7950
const std::shared_ptr<RuntimeManager> &runtimeManager) {
80-
const auto workletRuntime = runtimeManager->getRuntime(hostRuntimeId_);
81-
// NOLINTNEXTLINE(readability/braces)
82-
if (!workletRuntime) [[unlikely]] {
83-
return;
84-
}
85-
workletRuntime->schedule([resolver = shared_from_this(), resolveValue](jsi::Runtime &rt) {
86-
resolver->toJSValue(rt).getObject(rt).getFunction(rt).call(rt, resolveValue->toJSValue(rt));
87-
});
88-
}
89-
90-
SerializableRemoteFunction::RNOrigin::RNOriginProxy::RNOriginProxy(const std::shared_ptr<RNOrigin> &origin)
91-
: SerializableRemoteFunction(nullptr, origin->getHostRuntimeId(), origin->getName()), origin_(origin) {}
92-
93-
SerializableRemoteFunction::RNOrigin::RNOriginProxy::~RNOriginProxy() {
94-
origin_->getJSScheduler()->scheduleOnJS([id = origin_->getRemoteId()](jsi::Runtime &rt) {
95-
const auto registry = getRemoteFunctionRegistry(rt);
96-
registry.getPropertyAsFunction(rt, "delete").callWithThis(rt, registry, jsi::Value(id));
97-
});
98-
}
99-
100-
jsi::Value SerializableRemoteFunction::RNOrigin::RNOriginProxy::toJSValue(jsi::Runtime &rt) {
101-
if (&rt == origin_->getHostRuntime()) {
102-
return origin_->toJSValue(rt);
51+
if (isHostedOnRNRuntime()) {
52+
jsScheduler_->scheduleOnJS([resolver = shared_from_this(), resolveValue](jsi::Runtime &rt) {
53+
resolver->toJSValue(rt).getObject(rt).getFunction(rt).call(rt, resolveValue->toJSValue(rt));
54+
});
55+
} else {
56+
const auto workletRuntime = runtimeManager->getRuntime(hostRuntimeId_);
57+
// NOLINTNEXTLINE(readability/braces)
58+
if (!workletRuntime) [[unlikely]] {
59+
// Host runtime is dead, most likely we're the last owner of the Remote Function.
60+
// Do nothing.
61+
} else {
62+
workletRuntime->schedule([resolver = shared_from_this(), resolveValue](jsi::Runtime &rt) {
63+
resolver->toJSValue(rt).getObject(rt).getFunction(rt).call(rt, resolveValue->toJSValue(rt));
64+
});
65+
}
10366
}
104-
return unpackSelf(rt);
105-
}
106-
107-
void SerializableRemoteFunction::RNOrigin::RNOriginProxy::resolveOrRejectPromise(
108-
const std::shared_ptr<Serializable> &resolveValue,
109-
const std::shared_ptr<RuntimeManager> &runtimeManager) {
110-
origin_->resolveOrRejectPromise(resolveValue, runtimeManager);
11167
}
11268

11369
} // namespace worklets

0 commit comments

Comments
 (0)