Skip to content

Commit 74a7568

Browse files
mdvaccafacebook-github-bot
authored andcommitted
Fix race condition on startSurface
Summary: The root cause of this bug is a race condition between the onMeasure method and setupReactContext. ReactInstanceManager.attachRootView() method is responsible of the initialization of ReactRootViews and it is invoked by ReactRootView.onMeasure() method in the UIThread Important initialization steps: 1. Clear the Id of the ReactRootView 2. Add the ReactRootView to the mAttachedReactRoots 3. Call StartSurface (if the bridge has been initialized) Sometimes, when this method is invoked for the first time, the bridge is not initialized, in those cases we delay the start of the surface. Once the bridge is initialized, StartSurface is called by the setupReactContext() running in the NativeModuleThread. Since onMeasure can be called multiple times, it is possible that we call "StartSurface" twice for the same ReactRootView, causing the bug reported on T78832286. This diff adds an extra check to prevent calling "StartSurface" twice. The fix is done using an AtomicInteger comparison and it is gated by the flag "enableStartSurfaceRaceConditionFix". Once we verify this works fine in production we will clean up the code, remove the flags and maybe revisit the API of ReactRoot. changelog: [Android] Fix race-condition on the initialization of ReactRootViews Reviewed By: JoshuaGross Differential Revision: D25255877 fbshipit-source-id: ca8fb00f50e86891fb4c5a06240177cc1a0186d9
1 parent dee937c commit 74a7568

File tree

4 files changed

+50
-5
lines changed

4 files changed

+50
-5
lines changed

ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java

+24-5
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,9 @@ public void showDevOptionsDialog() {
795795
}
796796

797797
private void clearReactRoot(ReactRoot reactRoot) {
798+
if (ReactFeatureFlags.enableStartSurfaceRaceConditionFix) {
799+
reactRoot.getState().compareAndSet(ReactRoot.STATE_STARTED, ReactRoot.STATE_STOPPED);
800+
}
798801
reactRoot.getRootViewGroup().removeAllViews();
799802
reactRoot.getRootViewGroup().setId(View.NO_ID);
800803
}
@@ -810,17 +813,28 @@ private void clearReactRoot(ReactRoot reactRoot) {
810813
@ThreadConfined(UI)
811814
public void attachRootView(ReactRoot reactRoot) {
812815
UiThreadUtil.assertOnUiThread();
813-
mAttachedReactRoots.add(reactRoot);
814816

815-
// Reset reactRoot content as it's going to be populated by the application content from JS.
816-
clearReactRoot(reactRoot);
817+
// Calling clearReactRoot is necessary to initialize the Id on reactRoot
818+
// This is necessary independently if the RN Bridge has been initialized or not.
819+
// Ideally reactRoot should be initialized with id == NO_ID
820+
if (ReactFeatureFlags.enableStartSurfaceRaceConditionFix) {
821+
if (mAttachedReactRoots.add(reactRoot)) {
822+
clearReactRoot(reactRoot);
823+
}
824+
} else {
825+
mAttachedReactRoots.add(reactRoot);
826+
clearReactRoot(reactRoot);
827+
}
817828

818829
// If react context is being created in the background, JS application will be started
819830
// automatically when creation completes, as reactRoot reactRoot is part of the attached
820831
// reactRoot reactRoot list.
821832
ReactContext currentContext = getCurrentReactContext();
822833
if (mCreateReactContextThread == null && currentContext != null) {
823-
attachRootViewToInstance(reactRoot);
834+
if (!ReactFeatureFlags.enableStartSurfaceRaceConditionFix
835+
|| reactRoot.getState().compareAndSet(ReactRoot.STATE_STOPPED, ReactRoot.STATE_STARTED)) {
836+
attachRootViewToInstance(reactRoot);
837+
}
824838
}
825839
}
826840

@@ -1087,7 +1101,12 @@ private void setupReactContext(final ReactApplicationContext reactContext) {
10871101

10881102
ReactMarker.logMarker(ATTACH_MEASURED_ROOT_VIEWS_START);
10891103
for (ReactRoot reactRoot : mAttachedReactRoots) {
1090-
attachRootViewToInstance(reactRoot);
1104+
if (!ReactFeatureFlags.enableStartSurfaceRaceConditionFix
1105+
|| reactRoot
1106+
.getState()
1107+
.compareAndSet(ReactRoot.STATE_STOPPED, ReactRoot.STATE_STARTED)) {
1108+
attachRootViewToInstance(reactRoot);
1109+
}
10911110
}
10921111
ReactMarker.logMarker(ATTACH_MEASURED_ROOT_VIEWS_END);
10931112
}

ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java

+6
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import com.facebook.react.uimanager.common.UIManagerType;
5757
import com.facebook.react.uimanager.events.EventDispatcher;
5858
import com.facebook.systrace.Systrace;
59+
import java.util.concurrent.atomic.AtomicInteger;
5960

6061
/**
6162
* Default root view for catalyst apps. Provides the ability to listen for size changes so that a UI
@@ -98,6 +99,7 @@ public interface ReactRootViewEventListener {
9899
private int mLastOffsetX = Integer.MIN_VALUE;
99100
private int mLastOffsetY = Integer.MIN_VALUE;
100101
private @UIManagerType int mUIManagerType = DEFAULT;
102+
private final AtomicInteger mState = new AtomicInteger(STATE_STOPPED);
101103

102104
public ReactRootView(Context context) {
103105
super(context);
@@ -413,6 +415,10 @@ public String getSurfaceID() {
413415
return appProperties != null ? appProperties.getString("surfaceID") : null;
414416
}
415417

418+
public AtomicInteger getState() {
419+
return mState;
420+
}
421+
416422
public static Point getViewportOffset(View v) {
417423
int[] locationInWindow = new int[2];
418424
v.getLocationInWindow(locationInWindow);

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

+6
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,10 @@ public class ReactFeatureFlags {
9191

9292
/** Disable UI update operations in non-Fabric renderer after catalyst instance was destroyed */
9393
public static boolean disableNonFabricViewOperationsOnCatalystDestroy = false;
94+
95+
/**
96+
* Fixes race-condition in the initialization of RN surface. TODO T78832286: remove this flag once
97+
* we verify the fix is correct in production
98+
*/
99+
public static boolean enableStartSurfaceRaceConditionFix = false;
94100
}

ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactRoot.java

+14
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@
1111
import android.view.ViewGroup;
1212
import androidx.annotation.Nullable;
1313
import com.facebook.react.uimanager.common.UIManagerType;
14+
import java.util.concurrent.atomic.AtomicInteger;
1415

1516
/** Interface for the root native view of a React native application */
1617
public interface ReactRoot {
1718

19+
/** This constant represents that ReactRoot hasn't started yet or it has been destroyed. */
20+
int STATE_STOPPED = 0;
21+
/** This constant represents that ReactRoot has started. */
22+
int STATE_STARTED = 1;
23+
1824
/** Return cached launch properties for app */
1925
@Nullable
2026
Bundle getAppProperties();
@@ -58,4 +64,12 @@ public interface ReactRoot {
5864
@Deprecated
5965
@Nullable
6066
String getSurfaceID();
67+
68+
/**
69+
* This API is likely to change once the fix of T78832286 is confirmed TODO: T78832286 revisit
70+
* this API
71+
*
72+
* @return an {@link AtomicInteger} that represents the state of the ReactRoot object. WARNING:
73+
*/
74+
AtomicInteger getState();
6175
}

0 commit comments

Comments
 (0)