Skip to content

Commit 2951eb0

Browse files
committed
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`. (cherry picked from commit 325a644)
1 parent cf79cab commit 2951eb0

21 files changed

Lines changed: 118 additions & 115 deletions

File tree

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

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

339-
jsi_utils::addMethod<3>(
339+
jsi_utils::addMethod<2>(
340340
rt,
341341
obj,
342342
"createSerializableNonWorkletFunction",
343-
[jsScheduler = jsScheduler_, hostRuntimeId = hostRuntimeId_](
344-
jsi::Runtime &rt, const jsi::Value &, const jsi::Value(&args)[3]) {
343+
[jsScheduler = jsScheduler_, hostRuntimeId = hostRuntimeId_, rnRuntimeStatus = rnRuntimeStatus_](
344+
jsi::Runtime &rt, const jsi::Value &, const jsi::Value(&args)[2]) {
345345
auto fun = at<0>(args).getObject(rt).getFunction(rt);
346-
const auto name = at<2>(args).isUndefined() ? "" : at<2>(args).getString(rt).utf8(rt);
346+
const auto name = at<1>(args).isUndefined() ? "" : at<1>(args).getString(rt).utf8(rt);
347347
if (fun.isHostFunction(rt)) {
348348
return makeSerializableHostFunction(
349349
rt, fun.getHostFunction(rt), name, fun.getProperty(rt, "length").getNumber());
350+
} else if (hostRuntimeId == RuntimeData::rnRuntimeId) {
351+
return makeSerializableRemoteFunction(rt, name, fun, jsScheduler, rnRuntimeStatus);
352+
} else {
353+
return makeSerializableRemoteFunction(rt, name, std::move(fun), hostRuntimeId);
350354
}
351-
if (hostRuntimeId == RuntimeData::rnRuntimeId) {
352-
const int remoteId = static_cast<int>(at<1>(args).getNumber());
353-
auto ref = makeSerializableRemoteFunction(rt, name, remoteId, jsScheduler);
354-
ref.asObject(rt).setProperty(rt, "__keepAlive", true);
355-
return ref;
356-
}
357-
return makeSerializableRemoteFunction(rt, name, std::move(fun), hostRuntimeId);
358355
});
359356

360357
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.cpp

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@ jsi::Function getRemoteFunctionUnpacker(jsi::Runtime &rt) {
2828
return remoteFunctionUnpacker.asObject(rt).asFunction(rt);
2929
}
3030

31-
jsi::Object getRemoteFunctionRegistry(jsi::Runtime &rt) {
32-
auto registry = rt.global().getProperty(rt, "__remoteFunctionRegistry");
33-
react_native_assert(registry.isObject() && "remoteFunctionRegistry not found");
34-
return registry.getObject(rt);
35-
}
36-
3731
} // namespace
3832

3933
jsi::Value makeSerializableClone(
@@ -298,28 +292,19 @@ jsi::Value SerializableImport::toJSValue(jsi::Runtime &rt) {
298292

299293
SerializableRemoteFunction::~SerializableRemoteFunction() {
300294
if (isHostedOnRNRuntime()) {
301-
// TODO: consider batching
302-
const auto &data = std::get<RNRuntimeData>(runtimeData_);
303-
data.jsScheduler->scheduleOnJS([id = data.remoteId](jsi::Runtime &rt) {
304-
const auto registry = getRemoteFunctionRegistry(rt);
305-
registry.getPropertyAsFunction(rt, "delete").callWithThis(rt, registry, jsi::Value(id));
306-
});
295+
if (rnRuntimeStatus_->isDead()) {
296+
freeWithoutCallingDestructor(function_);
297+
} else {
298+
function_.reset();
299+
}
307300
} else {
308-
auto &workletData = std::get<WorkletRuntimeData>(runtimeData_);
309-
cleanupRuntimeAware(hostRuntime_, workletData.function);
301+
cleanupRuntimeAware(hostRuntime_, function_);
310302
}
311303
}
312304

313305
jsi::Value SerializableRemoteFunction::toJSValue(jsi::Runtime &rt) {
314306
if (&rt == hostRuntime_) {
315-
if (isHostedOnRNRuntime()) {
316-
const auto &rnData = std::get<RNRuntimeData>(runtimeData_);
317-
const auto registry = getRemoteFunctionRegistry(rt);
318-
return registry.getPropertyAsFunction(rt, "get").callWithThis(rt, registry, jsi::Value(rnData.remoteId));
319-
} else {
320-
const auto &workletData = std::get<WorkletRuntimeData>(runtimeData_);
321-
return jsi::Value(rt, *workletData.function);
322-
}
307+
return jsi::Value(rt, *function_);
323308
} else {
324309
const auto name = name_.empty() ? jsi::Value::undefined() : jsi::String::createFromUtf8(rt, name_);
325310
auto holderFunction = getRemoteFunctionUnpacker(rt).call(rt, name).getObject(rt);
@@ -333,8 +318,7 @@ void SerializableRemoteFunction::resolveOrRejectPromise(
333318
const std::shared_ptr<Serializable> &resolveValue,
334319
const std::shared_ptr<RuntimeManager> &runtimeManager) {
335320
if (isHostedOnRNRuntime()) {
336-
const auto &data = std::get<RNRuntimeData>(runtimeData_);
337-
data.jsScheduler->scheduleOnJS([resolver = shared_from_this(), resolveValue](jsi::Runtime &rt) {
321+
jsScheduler_->scheduleOnJS([resolver = shared_from_this(), resolveValue](jsi::Runtime &rt) {
338322
resolver->toJSValue(rt).getObject(rt).getFunction(rt).call(rt, resolveValue->toJSValue(rt));
339323
});
340324
} else {

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

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
#include <worklets/Compat/StableApi.h>
55
#include <worklets/Registries/WorkletRuntimeRegistry.h>
66
#include <worklets/Tools/JSScheduler.h>
7+
#include <worklets/Tools/RNRuntimeStatus.h>
78
#include <worklets/WorkletRuntime/RuntimeData.h>
89

910
#include <memory>
1011
#include <optional>
1112
#include <string>
1213
#include <utility>
13-
#include <variant>
1414
#include <vector>
1515

1616
using namespace facebook;
@@ -283,33 +283,21 @@ class RuntimeManager;
283283

284284
class SerializableRemoteFunction : public Serializable,
285285
public std::enable_shared_from_this<SerializableRemoteFunction> {
286-
private:
287-
struct RNRuntimeData {
288-
const int remoteId;
289-
const std::shared_ptr<JSScheduler> jsScheduler;
290-
};
291-
292-
struct WorkletRuntimeData {
293-
std::unique_ptr<jsi::Value> function;
294-
};
295-
296-
jsi::Runtime *hostRuntime_;
297-
const RuntimeData::RuntimeId hostRuntimeId_;
298-
std::variant<RNRuntimeData, WorkletRuntimeData> runtimeData_;
299-
const std::string name_;
300-
301286
public:
302287
/** Creates RN Runtime Remote Function. */
303288
SerializableRemoteFunction(
304289
jsi::Runtime &rnRuntime,
305290
const std::string &name,
306-
const int remoteId,
307-
const std::shared_ptr<JSScheduler> &jsScheduler)
291+
jsi::Function &&function,
292+
const std::shared_ptr<JSScheduler> &jsScheduler,
293+
const std::shared_ptr<RNRuntimeStatus> &rnRuntimeStatus)
308294
: Serializable(ValueType::RemoteFunctionType),
309295
hostRuntime_(&rnRuntime),
310296
hostRuntimeId_(RuntimeData::rnRuntimeId),
311-
runtimeData_(RNRuntimeData{.remoteId = remoteId, .jsScheduler = jsScheduler}),
312-
name_(name) {}
297+
function_(std::make_unique<jsi::Value>(rnRuntime, std::move(function))),
298+
name_(name),
299+
jsScheduler_(jsScheduler),
300+
rnRuntimeStatus_(rnRuntimeStatus) {}
313301

314302
/** Creates Worklet Runtime Remote Function. */
315303
SerializableRemoteFunction(
@@ -320,8 +308,10 @@ class SerializableRemoteFunction : public Serializable,
320308
: Serializable(ValueType::RemoteFunctionType),
321309
hostRuntime_(&workletRuntime),
322310
hostRuntimeId_(hostRuntimeId),
323-
runtimeData_(WorkletRuntimeData{.function = std::make_unique<jsi::Value>(workletRuntime, std::move(function))}),
324-
name_(name) {}
311+
function_(std::make_unique<jsi::Value>(workletRuntime, std::move(function))),
312+
name_(name),
313+
jsScheduler_(nullptr),
314+
rnRuntimeStatus_(nullptr) {}
325315

326316
~SerializableRemoteFunction() override;
327317

@@ -335,12 +325,20 @@ class SerializableRemoteFunction : public Serializable,
335325
jsi::Value toJSValue(jsi::Runtime &rt) override;
336326

337327
[[nodiscard]] bool isHostedOnRNRuntime() const noexcept {
338-
return std::holds_alternative<RNRuntimeData>(runtimeData_);
328+
return hostRuntimeId_ == RuntimeData::rnRuntimeId;
339329
}
340330

341331
[[nodiscard]] RuntimeData::RuntimeId getHostRuntimeId() const {
342332
return hostRuntimeId_;
343333
}
334+
335+
private:
336+
jsi::Runtime *hostRuntime_;
337+
const RuntimeData::RuntimeId hostRuntimeId_;
338+
std::unique_ptr<jsi::Value> function_;
339+
const std::string name_;
340+
const std::shared_ptr<JSScheduler> jsScheduler_;
341+
const std::shared_ptr<RNRuntimeStatus> rnRuntimeStatus_;
344342
};
345343

346344
class SerializableInitializer : public Serializable {

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,15 @@ jsi::Value makeSerializableHostFunction(
6363
jsi::Value makeSerializableRemoteFunction(
6464
jsi::Runtime &rnRuntime,
6565
const std::string &name,
66-
const int remoteId,
67-
const std::shared_ptr<JSScheduler> &jsScheduler) {
68-
auto serializable = std::make_shared<SerializableRemoteFunction>(rnRuntime, name, remoteId, jsScheduler);
66+
const jsi::Function &function,
67+
const std::shared_ptr<JSScheduler> &jsScheduler,
68+
const std::shared_ptr<RNRuntimeStatus> &rnRuntimeStatus) {
69+
auto serializable = std::make_shared<SerializableRemoteFunction>(
70+
rnRuntime,
71+
name,
72+
jsi::Value(rnRuntime, function).getObject(rnRuntime).getFunction(rnRuntime),
73+
jsScheduler,
74+
rnRuntimeStatus);
6975
return SerializableJSRef::newNativeStateObject(rnRuntime, serializable);
7076
}
7177

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ jsi::Value makeSerializableHostFunction(
6363
jsi::Value makeSerializableRemoteFunction(
6464
jsi::Runtime &rnRuntime,
6565
const std::string &name,
66-
int remoteId,
67-
const std::shared_ptr<JSScheduler> &jsScheduler);
66+
const jsi::Function &function,
67+
const std::shared_ptr<JSScheduler> &jsScheduler,
68+
const std::shared_ptr<RNRuntimeStatus> &rnRuntimeStatus);
6869

6970
/** Creates Worklet Runtime Remote Function. */
7071
jsi::Value makeSerializableRemoteFunction(
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#pragma once
2+
3+
#include <atomic>
4+
5+
namespace worklets {
6+
7+
class RNRuntimeStatus {
8+
public:
9+
[[nodiscard]] bool isDead() const {
10+
return isDead_;
11+
}
12+
13+
void setDead() {
14+
isDead_ = true;
15+
}
16+
17+
private:
18+
std::atomic_bool isDead_ = false;
19+
};
20+
21+
} // namespace worklets

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@ WorkletsModule::WorkletsModule(
3333
const std::shared_ptr<UIScheduler> &uiScheduler)
3434
: javaPart_(jni::make_global(jThis)),
3535
rnRuntime_(rnRuntime),
36+
rnRuntimeStatus_(std::make_shared<RNRuntimeStatus>()),
3637
workletsModuleProxy_(std::make_shared<WorkletsModuleProxy>(
3738
*rnRuntime,
3839
jsCallInvoker,
3940
uiScheduler,
4041
getIsOnJSQueueThread(),
4142
getRuntimeBindings(bundleModeConfig.enabled, *rnRuntime),
42-
bundleModeConfig)) {}
43+
bundleModeConfig,
44+
rnRuntimeStatus_)) {}
4345

4446
jni::local_ref<WorkletsModule::jhybriddata> WorkletsModule::initHybrid(
4547
jni::alias_ref<jhybridobject> jThis, // NOLINT //(performance-unnecessary-value-param)
@@ -179,6 +181,7 @@ std::function<bool()> WorkletsModule::getIsOnJSQueueThread() {
179181
}
180182

181183
void WorkletsModule::invalidateCpp() {
184+
rnRuntimeStatus_->setDead();
182185
javaPart_.reset();
183186
workletsModuleProxy_.reset();
184187
}

0 commit comments

Comments
 (0)