diff --git a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/TurboModuleManager.java b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/TurboModuleManager.java index 67a9dd48316..52647dd0129 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/TurboModuleManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/TurboModuleManager.java @@ -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 mEagerInitModuleNames; + private final TurboModuleProvider mJavaModuleProvider; + private final TurboModuleProvider mCxxModuleProvider; - private final Map 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 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() : 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 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. + * + *

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 getModules() { + final List turboModuleHolders = new ArrayList<>(); + synchronized (mTurboModuleCleanupLock) { + turboModuleHolders.addAll(mTurboModuleHolders.values()); + } + + final List 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 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. + * + *

After this point, mTurboModuleHolders will not be accessed by TurboModuleManager. + * Therefore, it won't be modified. + * + *

The TurboModuleHolders in mTurboModuleHolders, however, can still be populated with newly + * created TurboModules. + */ + synchronized (mTurboModuleCleanupLock) { + mTurboModuleCleanupStarted = true; } - mTurboModules.clear(); + final Set 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); + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp index 6131f06a9ac..9d1cb27475b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/TurboModuleManager.cpp @@ -93,10 +93,10 @@ void TurboModuleManager::installJSIBindings() { } static auto getLegacyCxxModule = - delegate->getClass() + javaPart->getClass() ->getMethod( const std::string &)>("getLegacyCxxModule"); - auto legacyCxxModule = getLegacyCxxModule(delegate.get(), name); + auto legacyCxxModule = getLegacyCxxModule(javaPart.get(), name); if (legacyCxxModule) { auto turboModule = std::make_shared(