Experiment to replace Fabric MountItem lists with concurrent queues

Summary:
Currently, queuing any sort of MountItem or getting the list of MountItems requires synchronization. Since these can be queued up at any point from the JS thread, there will, in theory, be constant contention between JS and UI thread.

To see if this is true, I'm creating an experiment instead of just switching over to concurrent structures. After seeing results, we can hopefully make a decision and delete the non-concurrent stuff or improve on it somehow.

Changelog: [Internal]

Reviewed By: ejanzer

Differential Revision: D24942110

fbshipit-source-id: fcbdeda51f91f4bd430a20d7484854fb1e94a832
This commit is contained in:
Joshua Gross
2020-11-13 21:51:04 -08:00
committed by Facebook GitHub Bot
parent 480dabd665
commit 23def0f8f0
2 changed files with 144 additions and 61 deletions
@@ -88,4 +88,7 @@ public class ReactFeatureFlags {
/** Potential bugfix for crashes caused by mutating the view hierarchy during onDraw. */
public static boolean enableDrawMutationFix = true;
/** Use lock-free data structures for Fabric MountItems. */
public static boolean enableLockFreeMountInstructions = false;
}
@@ -89,10 +89,12 @@ import com.facebook.react.views.text.TextLayoutManager;
import com.facebook.systrace.Systrace;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
@SuppressLint("MissingNativeLoadLibrary")
@@ -124,20 +126,35 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
new ConcurrentHashMap<>();
@NonNull private final EventBeatManager mEventBeatManager;
@NonNull private final Object mViewCommandMountItemsLock = new Object();
@NonNull private final Object mMountItemsLock = new Object();
@NonNull private final Object mPreMountItemsLock = new Object();
private boolean mInDispatch = false;
private int mReDispatchCounter = 0;
@NonNull
private final CopyOnWriteArrayList<UIManagerListener> mListeners = new CopyOnWriteArrayList<>();
// Concurrent MountItem data-structures, experimental. TODO: T79662803
@NonNull
private final ConcurrentLinkedQueue<DispatchCommandMountItem> mViewCommandMountItemsConcurrent =
new ConcurrentLinkedQueue<>();
@NonNull
private final ConcurrentLinkedQueue<MountItem> mMountItemsConcurrent =
new ConcurrentLinkedQueue<>();
@NonNull
private final ConcurrentLinkedQueue<PreAllocateViewMountItem> mPreMountItemsConcurrent =
new ConcurrentLinkedQueue<>();
// Non-concurrent MountItem data-structures
@NonNull private final Object mViewCommandMountItemsLock = new Object();
@NonNull private final Object mMountItemsLock = new Object();
@NonNull private final Object mPreMountItemsLock = new Object();
@GuardedBy("mViewCommandMountItemsLock")
@NonNull
private List<DispatchCommandMountItem> mViewCommandMountItems = new ArrayList<>();
@NonNull
private final CopyOnWriteArrayList<UIManagerListener> mListeners = new CopyOnWriteArrayList<>();
@GuardedBy("mMountItemsLock")
@NonNull
private List<MountItem> mMountItems = new ArrayList<>();
@@ -339,18 +356,15 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
// possible at teardown, but this race should *never* happen at startup.
@Nullable ThemedReactContext context = mReactContextForRootTag.get(rootTag);
String component = getFabricComponentName(componentName);
synchronized (mPreMountItemsLock) {
mPreMountItems.add(
new PreAllocateViewMountItem(
context,
rootTag,
reactTag,
component,
props,
(StateWrapper) stateWrapper,
isLayoutable));
}
addPreAllocateMountItem(
new PreAllocateViewMountItem(
context,
rootTag,
reactTag,
getFabricComponentName(componentName),
props,
(StateWrapper) stateWrapper,
isLayoutable));
}
@DoNotStrip
@@ -616,9 +630,7 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
// If the reactTag exists, we assume that it might at the end of the next
// batch of MountItems. Otherwise, we try to execute immediately.
if (!mMountingManager.getViewExists(reactTag)) {
synchronized (mMountItemsLock) {
mMountItems.add(synchronousMountItem);
}
addMountItem(synchronousMountItem);
return;
}
@@ -692,9 +704,7 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
}
if (shouldSchedule && mountItem != null) {
synchronized (mMountItemsLock) {
mMountItems.add(mountItem);
}
addMountItem(mountItem);
if (UiThreadUtil.isOnUiThread()) {
// We only read these flags on the UI thread.
tryDispatchMountItems();
@@ -777,9 +787,28 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
mLastExecutedMountItemSurfaceId = -1;
}
@Nullable
private <E extends MountItem> List<E> drainConcurrentItemQueue(ConcurrentLinkedQueue<E> queue) {
List<E> result = new ArrayList<>();
while (!queue.isEmpty()) {
E item = queue.poll();
if (item != null) {
result.add(item);
}
}
if (result.size() == 0) {
return null;
}
return result;
}
@UiThread
@ThreadConfined(UI)
private List<DispatchCommandMountItem> getAndResetViewCommandMountItems() {
if (ReactFeatureFlags.enableLockFreeMountInstructions) {
return drainConcurrentItemQueue(mViewCommandMountItemsConcurrent);
}
synchronized (mViewCommandMountItemsLock) {
List<DispatchCommandMountItem> result = mViewCommandMountItems;
if (result.isEmpty()) {
@@ -793,6 +822,10 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
@UiThread
@ThreadConfined(UI)
private List<MountItem> getAndResetMountItems() {
if (ReactFeatureFlags.enableLockFreeMountInstructions) {
return drainConcurrentItemQueue(mMountItemsConcurrent);
}
synchronized (mMountItemsLock) {
List<MountItem> result = mMountItems;
if (result.isEmpty()) {
@@ -803,7 +836,11 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
}
}
private ArrayDeque<PreAllocateViewMountItem> getAndResetPreMountItems() {
private Collection<PreAllocateViewMountItem> getAndResetPreMountItems() {
if (ReactFeatureFlags.enableLockFreeMountInstructions) {
return drainConcurrentItemQueue(mPreMountItemsConcurrent);
}
synchronized (mPreMountItemsLock) {
ArrayDeque<PreAllocateViewMountItem> result = mPreMountItems;
if (result.isEmpty()) {
@@ -926,19 +963,18 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
// If there are MountItems to dispatch, we make sure all the "pre mount items" are executed
// first
ArrayDeque<PreAllocateViewMountItem> mPreMountItemsToDispatch = getAndResetPreMountItems();
Collection<PreAllocateViewMountItem> preMountItemsToDispatch = getAndResetPreMountItems();
if (mPreMountItemsToDispatch != null) {
if (preMountItemsToDispatch != null) {
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
"FabricUIManager::mountViews preMountItems to execute: "
+ mPreMountItemsToDispatch.size());
+ preMountItemsToDispatch.size());
while (!mPreMountItemsToDispatch.isEmpty()) {
PreAllocateViewMountItem mountItem = mPreMountItemsToDispatch.pollFirst();
for (PreAllocateViewMountItem preMountItem : preMountItemsToDispatch) {
if (surfaceActiveForExecution(
mountItem.getRootTag(), "dispatchMountItems PreAllocateViewMountItem")) {
mountItem.execute(mMountingManager);
preMountItem.getRootTag(), "dispatchMountItems PreAllocateViewMountItem")) {
preMountItem.execute(mMountingManager);
}
}
@@ -1016,12 +1052,19 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
break;
}
PreAllocateViewMountItem preMountItemToDispatch;
synchronized (mPreMountItemsLock) {
if (mPreMountItems.isEmpty()) {
break;
PreAllocateViewMountItem preMountItemToDispatch = null;
if (ReactFeatureFlags.enableLockFreeMountInstructions) {
preMountItemToDispatch = mPreMountItemsConcurrent.poll();
} else {
synchronized (mPreMountItemsLock) {
if (!mPreMountItems.isEmpty()) {
preMountItemToDispatch = mPreMountItems.pollFirst();
}
}
preMountItemToDispatch = mPreMountItems.pollFirst();
}
// If list is empty, `poll` will return null, or var will never be set
if (preMountItemToDispatch == null) {
break;
}
if (surfaceActiveForExecution(
@@ -1132,18 +1175,14 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
@AnyThread
@ThreadConfined(ANY)
private void dispatchCommandMountItem(DispatchCommandMountItem command) {
synchronized (mViewCommandMountItemsLock) {
mViewCommandMountItems.add(command);
}
addViewCommandMountItem(command);
}
@Override
@AnyThread
@ThreadConfined(ANY)
public void sendAccessibilityEvent(int reactTag, int eventType) {
synchronized (mMountItemsLock) {
mMountItems.add(new SendAccessibilityEvent(reactTag, eventType));
}
addMountItem(new SendAccessibilityEvent(reactTag, eventType));
}
/**
@@ -1156,15 +1195,13 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
@DoNotStrip
public void setJSResponder(
final int reactTag, final int initialReactTag, final boolean blockNativeResponder) {
synchronized (mMountItemsLock) {
mMountItems.add(
new MountItem() {
@Override
public void execute(MountingManager mountingManager) {
mountingManager.setJSResponder(reactTag, initialReactTag, blockNativeResponder);
}
});
}
addMountItem(
new MountItem() {
@Override
public void execute(MountingManager mountingManager) {
mountingManager.setJSResponder(reactTag, initialReactTag, blockNativeResponder);
}
});
}
/**
@@ -1173,15 +1210,13 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
*/
@DoNotStrip
public void clearJSResponder() {
synchronized (mMountItemsLock) {
mMountItems.add(
new MountItem() {
@Override
public void execute(MountingManager mountingManager) {
mountingManager.clearJSResponder();
}
});
}
addMountItem(
new MountItem() {
@Override
public void execute(MountingManager mountingManager) {
mountingManager.clearJSResponder();
}
});
}
@Override
@@ -1229,6 +1264,51 @@ public class FabricUIManager implements UIManager, LifecycleEventListener {
return performanceCounters;
}
/**
* Abstraction between concurrent and non-concurrent MountItem list.
*
* @param mountItem
*/
private void addMountItem(MountItem mountItem) {
if (ReactFeatureFlags.enableLockFreeMountInstructions) {
mMountItemsConcurrent.add(mountItem);
} else {
synchronized (mMountItemsLock) {
mMountItems.add(mountItem);
}
}
}
/**
* Abstraction between concurrent and non-concurrent PreAllocateViewMountItem list.
*
* @param mountItem
*/
private void addPreAllocateMountItem(PreAllocateViewMountItem mountItem) {
if (ReactFeatureFlags.enableLockFreeMountInstructions) {
mPreMountItemsConcurrent.add(mountItem);
} else {
synchronized (mPreMountItemsLock) {
mPreMountItemsConcurrent.add(mountItem);
}
}
}
/**
* Abstraction between concurrent and non-concurrent DispatchCommandMountItem list.
*
* @param mountItem
*/
private void addViewCommandMountItem(DispatchCommandMountItem mountItem) {
if (ReactFeatureFlags.enableLockFreeMountInstructions) {
mViewCommandMountItemsConcurrent.add(mountItem);
} else {
synchronized (mViewCommandMountItemsLock) {
mViewCommandMountItems.add(mountItem);
}
}
}
private class DispatchUIFrameCallback extends GuardedFrameCallback {
private volatile boolean mIsMountingEnabled = true;