mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Make TurboModule creation thread-safe
Summary: NativeModules can be created from any number of threads. In the legacy system, `ModuleHolder`, the class responsible for creating NativeModules, has built-in concurrency control to ensure that NativeModule creation is thread-safe. This diff introduces that thread-safety to the TurboModule infra. Basically, after this diff, if `n` threads race to create a TurboModule x, only the first thread will create x. All other threads will wait until x is created. Changelog: [Android][Fixed] - Make TurboModule creation thread-safe Reviewed By: mdvacca Differential Revision: D20659799 fbshipit-source-id: 2b720fe1ea49e40ae0d6dae50d422f23a6f45520
This commit is contained in:
committed by
Facebook GitHub Bot
parent
bd3868643d
commit
d36fa783a9
+255
-44
@@ -7,7 +7,10 @@
|
||||
|
||||
package com.facebook.react.turbomodule.core;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.bridge.CxxModuleWrapper;
|
||||
@@ -26,11 +29,20 @@ import java.util.*;
|
||||
* a Java module, that the C++ counterpart calls.
|
||||
*/
|
||||
public class TurboModuleManager implements JSIModule, TurboModuleRegistry {
|
||||
|
||||
private final TurboModuleManagerDelegate mTurbomoduleManagerDelegate;
|
||||
private static volatile boolean sIsSoLibraryLoaded;
|
||||
private final List<String> mEagerInitModuleNames;
|
||||
private final TurboModuleProvider mJavaModuleProvider;
|
||||
private final TurboModuleProvider mCxxModuleProvider;
|
||||
|
||||
private final Map<String, TurboModule> mTurboModules = new HashMap<>();
|
||||
// Prevents the creation of new TurboModules once cleanup as been initiated.
|
||||
private final Object mTurboModuleCleanupLock = new Object();
|
||||
|
||||
@GuardedBy("mTurboModuleCleanupLock")
|
||||
private boolean mTurboModuleCleanupStarted = false;
|
||||
|
||||
// List of TurboModules that have been, or are currently being, instantiated
|
||||
@GuardedBy("mTurboModuleCleanupLock")
|
||||
private final Map<String, TurboModuleHolder> mTurboModuleHolders = new HashMap<>();
|
||||
|
||||
@DoNotStrip
|
||||
@SuppressWarnings("unused")
|
||||
@@ -38,7 +50,7 @@ public class TurboModuleManager implements JSIModule, TurboModuleRegistry {
|
||||
|
||||
public TurboModuleManager(
|
||||
JavaScriptContextHolder jsContext,
|
||||
TurboModuleManagerDelegate tmmDelegate,
|
||||
@Nullable final TurboModuleManagerDelegate delegate,
|
||||
CallInvokerHolder jsCallInvokerHolder,
|
||||
CallInvokerHolder nativeCallInvokerHolder) {
|
||||
maybeLoadSoLibrary();
|
||||
@@ -47,70 +59,212 @@ public class TurboModuleManager implements JSIModule, TurboModuleRegistry {
|
||||
jsContext.get(),
|
||||
(CallInvokerHolderImpl) jsCallInvokerHolder,
|
||||
(CallInvokerHolderImpl) nativeCallInvokerHolder,
|
||||
tmmDelegate);
|
||||
mTurbomoduleManagerDelegate = tmmDelegate;
|
||||
delegate);
|
||||
installJSIBindings();
|
||||
|
||||
mEagerInitModuleNames =
|
||||
delegate == null ? new ArrayList<String>() : delegate.getEagerInitModuleNames();
|
||||
|
||||
mJavaModuleProvider =
|
||||
new TurboModuleProvider() {
|
||||
@Nullable
|
||||
public TurboModule getModule(String moduleName) {
|
||||
if (delegate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return delegate.getModule(moduleName);
|
||||
}
|
||||
};
|
||||
|
||||
mCxxModuleProvider =
|
||||
new TurboModuleProvider() {
|
||||
@Nullable
|
||||
public TurboModule getModule(String moduleName) {
|
||||
if (delegate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CxxModuleWrapper nativeModule = delegate.getLegacyCxxModule(moduleName);
|
||||
if (nativeModule != null) {
|
||||
// TurboModuleManagerDelegate must always return TurboModules
|
||||
Assertions.assertCondition(
|
||||
nativeModule instanceof TurboModule,
|
||||
"CxxModuleWrapper \"" + moduleName + "\" is not a TurboModule");
|
||||
return (TurboModule) nativeModule;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public List<String> getEagerInitModuleNames() {
|
||||
return mTurbomoduleManagerDelegate.getEagerInitModuleNames();
|
||||
return mEagerInitModuleNames;
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
/**
|
||||
* TurboModuleHolders are used as locks to ensure that when n threads race to create a
|
||||
* TurboModule, only the first thread creates that TurboModule. All other n - 1 threads wait until
|
||||
* the TurboModule is created and initialized.
|
||||
*/
|
||||
@Nullable
|
||||
private TurboModule getJavaModule(String name) {
|
||||
if (!mTurboModules.containsKey(name)) {
|
||||
final TurboModule turboModule = mTurbomoduleManagerDelegate.getModule(name);
|
||||
private TurboModuleHolder getOrMaybeCreateTurboModuleHolder(String moduleName) {
|
||||
synchronized (mTurboModuleCleanupLock) {
|
||||
if (mTurboModuleCleanupStarted) {
|
||||
/*
|
||||
* Always return null after cleanup has started, so that getModule(moduleName) returns null.
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO(T64619790): Should we populate mJavaTurboModuleHolders ahead of time, to avoid having
|
||||
* * to control concurrent access to it?
|
||||
*/
|
||||
if (!mTurboModuleHolders.containsKey(moduleName)) {
|
||||
mTurboModuleHolders.put(moduleName, new TurboModuleHolder());
|
||||
}
|
||||
return mTurboModuleHolders.get(moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If n threads race to create TurboModule x, then only the first thread should create x. All n -
|
||||
* 1 other threads should wait until x is created and initialized.
|
||||
*
|
||||
* <p>Note: After we've started cleanup, getModule will always return null.
|
||||
*/
|
||||
@Nullable
|
||||
public TurboModule getModule(String moduleName) {
|
||||
final TurboModuleHolder moduleHolder = getOrMaybeCreateTurboModuleHolder(moduleName);
|
||||
|
||||
if (moduleHolder == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean shouldCreateModule = false;
|
||||
|
||||
synchronized (moduleHolder) {
|
||||
if (moduleHolder.isDoneCreatingModule()) {
|
||||
return moduleHolder.getModule();
|
||||
}
|
||||
|
||||
if (!moduleHolder.isCreatingModule()) {
|
||||
// Only one thread gets here
|
||||
shouldCreateModule = true;
|
||||
moduleHolder.startCreatingModule();
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCreateModule) {
|
||||
TurboModule turboModule = mJavaModuleProvider.getModule(moduleName);
|
||||
|
||||
if (turboModule == null) {
|
||||
turboModule = mCxxModuleProvider.getModule(moduleName);
|
||||
}
|
||||
|
||||
if (turboModule != null) {
|
||||
/**
|
||||
* TurboModuleManager is initialized after ReactApplicationContext has been setup.
|
||||
* Therefore, it's safe to call initialize on the TurboModule.
|
||||
synchronized (moduleHolder) {
|
||||
moduleHolder.setModule(turboModule);
|
||||
}
|
||||
|
||||
/*
|
||||
* TurboModuleManager is initialized after ReactApplicationContext has been set up.
|
||||
* NativeModules should be initialized after ReactApplicationContext has been set up.
|
||||
* Therefore, we should initialize on the TurboModule now.
|
||||
*/
|
||||
((NativeModule) turboModule).initialize();
|
||||
|
||||
mTurboModules.put(name, turboModule);
|
||||
}
|
||||
|
||||
synchronized (moduleHolder) {
|
||||
moduleHolder.endCreatingModule();
|
||||
moduleHolder.notifyAll();
|
||||
}
|
||||
|
||||
return turboModule;
|
||||
}
|
||||
|
||||
return mTurboModules.get(name);
|
||||
synchronized (moduleHolder) {
|
||||
boolean wasInterrupted = false;
|
||||
while (moduleHolder.isCreatingModule()) {
|
||||
try {
|
||||
// Wait until TurboModule is created and initialized
|
||||
moduleHolder.wait();
|
||||
} catch (InterruptedException e) {
|
||||
wasInterrupted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (wasInterrupted) {
|
||||
/*
|
||||
* TurboModules should ideally be quick to create and initialize. Therefore,
|
||||
* we wait until the TurboModule is done initializing before re-interrupting the
|
||||
* current thread.
|
||||
*/
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
return moduleHolder.getModule();
|
||||
}
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
@Nullable
|
||||
private TurboModule getLegacyCxxModule(String name) {
|
||||
if (!mTurboModules.containsKey(name)) {
|
||||
final CxxModuleWrapper turboModule = mTurbomoduleManagerDelegate.getLegacyCxxModule(name);
|
||||
private CxxModuleWrapper getLegacyCxxModule(String moduleName) {
|
||||
final TurboModule turboModule = getModule(moduleName);
|
||||
if (!(turboModule instanceof CxxModuleWrapper)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (turboModule instanceof TurboModule) {
|
||||
/**
|
||||
* TurboModuleManager is initialized after ReactApplicationContext has been setup.
|
||||
* Therefore, it's safe to call initialize on the TurboModule.
|
||||
*/
|
||||
((NativeModule) turboModule).initialize();
|
||||
return (CxxModuleWrapper) turboModule;
|
||||
}
|
||||
|
||||
mTurboModules.put(name, (TurboModule) turboModule);
|
||||
@DoNotStrip
|
||||
@Nullable
|
||||
private TurboModule getJavaModule(String moduleName) {
|
||||
final TurboModule turboModule = getModule(moduleName);
|
||||
if (turboModule instanceof CxxModuleWrapper) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return turboModule;
|
||||
}
|
||||
|
||||
/** Which TurboModules have been created? */
|
||||
public Collection<TurboModule> getModules() {
|
||||
final List<TurboModuleHolder> turboModuleHolders = new ArrayList<>();
|
||||
synchronized (mTurboModuleCleanupLock) {
|
||||
turboModuleHolders.addAll(mTurboModuleHolders.values());
|
||||
}
|
||||
|
||||
final List<TurboModule> turboModules = new ArrayList<>();
|
||||
for (final TurboModuleHolder moduleHolder : turboModuleHolders) {
|
||||
synchronized (moduleHolder) {
|
||||
// No need to wait for the TurboModule to finish being created and initialized
|
||||
if (moduleHolder.getModule() != null) {
|
||||
turboModules.add(moduleHolder.getModule());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mTurboModules.get(name);
|
||||
return turboModules;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TurboModule getModule(String name) {
|
||||
TurboModule javaModule = getJavaModule(name);
|
||||
if (javaModule != null) {
|
||||
return javaModule;
|
||||
public boolean hasModule(String moduleName) {
|
||||
TurboModuleHolder moduleHolder;
|
||||
synchronized (mTurboModuleCleanupLock) {
|
||||
moduleHolder = mTurboModuleHolders.get(moduleName);
|
||||
}
|
||||
return getLegacyCxxModule(name);
|
||||
}
|
||||
|
||||
public Collection<TurboModule> getModules() {
|
||||
return mTurboModules.values();
|
||||
}
|
||||
if (moduleHolder != null) {
|
||||
synchronized (moduleHolder) {
|
||||
if (moduleHolder.getModule() != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasModule(String name) {
|
||||
return mTurboModules.containsKey(name);
|
||||
return false;
|
||||
}
|
||||
|
||||
private native HybridData initHybrid(
|
||||
@@ -126,12 +280,32 @@ public class TurboModuleManager implements JSIModule, TurboModuleRegistry {
|
||||
|
||||
@Override
|
||||
public void onCatalystInstanceDestroy() {
|
||||
for (TurboModule turboModule : mTurboModules.values()) {
|
||||
// TODO(T48014458): Rename this to invalidate()
|
||||
((NativeModule) turboModule).onCatalystInstanceDestroy();
|
||||
/*
|
||||
* Halt the production of new TurboModules.
|
||||
*
|
||||
* <p>After this point, mTurboModuleHolders will not be accessed by TurboModuleManager.
|
||||
* Therefore, it won't be modified.
|
||||
*
|
||||
* <p>The TurboModuleHolders in mTurboModuleHolders, however, can still be populated with newly
|
||||
* created TurboModules.
|
||||
*/
|
||||
synchronized (mTurboModuleCleanupLock) {
|
||||
mTurboModuleCleanupStarted = true;
|
||||
}
|
||||
|
||||
mTurboModules.clear();
|
||||
final Set<String> turboModuleNames = new HashSet<>(mTurboModuleHolders.keySet());
|
||||
|
||||
for (final String moduleName : turboModuleNames) {
|
||||
// Retrieve the TurboModule, possibly waiting for it to finish instantiating.
|
||||
final TurboModule turboModule = getModule(moduleName);
|
||||
|
||||
if (turboModule != null) {
|
||||
// TODO(T48014458): Rename this to invalidate()
|
||||
((NativeModule) turboModule).onCatalystInstanceDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
mTurboModuleHolders.clear();
|
||||
|
||||
// Delete the native part of this hybrid class.
|
||||
mHybridData.resetNative();
|
||||
@@ -144,4 +318,41 @@ public class TurboModuleManager implements JSIModule, TurboModuleRegistry {
|
||||
sIsSoLibraryLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TurboModuleHolder {
|
||||
private volatile TurboModule mModule = null;
|
||||
private volatile boolean mIsTryingToCreate = false;
|
||||
private volatile boolean mIsDoneCreatingModule = false;
|
||||
|
||||
void setModule(@NonNull TurboModule module) {
|
||||
mModule = module;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
TurboModule getModule() {
|
||||
return mModule;
|
||||
}
|
||||
|
||||
void startCreatingModule() {
|
||||
mIsTryingToCreate = true;
|
||||
}
|
||||
|
||||
void endCreatingModule() {
|
||||
mIsTryingToCreate = false;
|
||||
mIsDoneCreatingModule = true;
|
||||
}
|
||||
|
||||
boolean isDoneCreatingModule() {
|
||||
return mIsDoneCreatingModule;
|
||||
}
|
||||
|
||||
boolean isCreatingModule() {
|
||||
return mIsTryingToCreate;
|
||||
}
|
||||
}
|
||||
|
||||
private interface TurboModuleProvider {
|
||||
@Nullable
|
||||
TurboModule getModule(String name);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -93,10 +93,10 @@ void TurboModuleManager::installJSIBindings() {
|
||||
}
|
||||
|
||||
static auto getLegacyCxxModule =
|
||||
delegate->getClass()
|
||||
javaPart->getClass()
|
||||
->getMethod<jni::alias_ref<CxxModuleWrapper::javaobject>(
|
||||
const std::string &)>("getLegacyCxxModule");
|
||||
auto legacyCxxModule = getLegacyCxxModule(delegate.get(), name);
|
||||
auto legacyCxxModule = getLegacyCxxModule(javaPart.get(), name);
|
||||
|
||||
if (legacyCxxModule) {
|
||||
auto turboModule = std::make_shared<react::TurboCxxModule>(
|
||||
|
||||
Reference in New Issue
Block a user