Skip to content

Commit f2658b6

Browse files
committed
Now using a thread lock on UIImplementation methods that mutate the node children sparse array
- Fixes: #17178 (comment)
1 parent ea734dc commit f2658b6

File tree

1 file changed

+142
-131
lines changed

1 file changed

+142
-131
lines changed

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

Lines changed: 142 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
* shadow node hierarchy that is then mapped to a native view hierarchy.
4444
*/
4545
public class UIImplementation {
46+
// Lock needed in order to fix https://github.com/facebook/react-native/issues/17178#issuecomment-395858863
47+
protected Object uiImplementationThreadLock = new Object();
4648

4749
protected final EventDispatcher mEventDispatcher;
4850
protected final ReactApplicationContext mReactContext;
@@ -197,23 +199,25 @@ public void updateRootView(
197199
*/
198200
public <T extends SizeMonitoringFrameLayout & MeasureSpecProvider> void registerRootView(
199201
T rootView, int tag, ThemedReactContext context) {
200-
final ReactShadowNode rootCSSNode = createRootShadowNode();
201-
rootCSSNode.setReactTag(tag);
202-
rootCSSNode.setThemedContext(context);
203-
204-
int widthMeasureSpec = rootView.getWidthMeasureSpec();
205-
int heightMeasureSpec = rootView.getHeightMeasureSpec();
206-
updateRootView(rootCSSNode, widthMeasureSpec, heightMeasureSpec);
207-
208-
context.runOnNativeModulesQueueThread(new Runnable() {
209-
@Override
210-
public void run() {
211-
mShadowNodeRegistry.addRootNode(rootCSSNode);
212-
}
213-
});
202+
synchronized (uiImplementationThreadLock) {
203+
final ReactShadowNode rootCSSNode = createRootShadowNode();
204+
rootCSSNode.setReactTag(tag); // Thread safety needed here
205+
rootCSSNode.setThemedContext(context);
206+
207+
int widthMeasureSpec = rootView.getWidthMeasureSpec();
208+
int heightMeasureSpec = rootView.getHeightMeasureSpec();
209+
updateRootView(rootCSSNode, widthMeasureSpec, heightMeasureSpec);
210+
211+
context.runOnNativeModulesQueueThread(new Runnable() {
212+
@Override
213+
public void run() {
214+
mShadowNodeRegistry.addRootNode(rootCSSNode);
215+
}
216+
});
214217

215-
// register it within NativeViewHierarchyManager
216-
mOperationsQueue.addRootView(tag, rootView, context);
218+
// register it within NativeViewHierarchyManager
219+
mOperationsQueue.addRootView(tag, rootView, context);
220+
}
217221
}
218222

219223
/**
@@ -228,7 +232,9 @@ public void removeRootView(int rootViewTag) {
228232
* Unregisters a root node with a given tag from the shadow node registry
229233
*/
230234
public void removeRootShadowNode(int rootViewTag) {
231-
mShadowNodeRegistry.removeRootNode(rootViewTag);
235+
synchronized (uiImplementationThreadLock) {
236+
mShadowNodeRegistry.removeRootNode(rootViewTag); // Thread safety needed here
237+
}
232238
}
233239

234240
/**
@@ -279,23 +285,25 @@ public Map<String, Long> getProfiledBatchPerfCounters() {
279285
* Invoked by React to create a new node with a given tag, class name and properties.
280286
*/
281287
public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
282-
ReactShadowNode cssNode = createShadowNode(className);
283-
ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
284-
Assertions.assertNotNull(rootNode, "Root node with tag " + rootViewTag + " doesn't exist");
285-
cssNode.setReactTag(tag);
286-
cssNode.setViewClassName(className);
287-
cssNode.setRootTag(rootNode.getReactTag());
288-
cssNode.setThemedContext(rootNode.getThemedContext());
289-
290-
mShadowNodeRegistry.addNode(cssNode);
288+
synchronized (uiImplementationThreadLock) {
289+
ReactShadowNode cssNode = createShadowNode(className);
290+
ReactShadowNode rootNode = mShadowNodeRegistry.getNode(rootViewTag);
291+
Assertions.assertNotNull(rootNode, "Root node with tag " + rootViewTag + " doesn't exist");
292+
cssNode.setReactTag(tag); // Thread safety needed here
293+
cssNode.setViewClassName(className);
294+
cssNode.setRootTag(rootNode.getReactTag());
295+
cssNode.setThemedContext(rootNode.getThemedContext());
296+
297+
mShadowNodeRegistry.addNode(cssNode);
298+
299+
ReactStylesDiffMap styles = null;
300+
if (props != null) {
301+
styles = new ReactStylesDiffMap(props);
302+
cssNode.updateProperties(styles);
303+
}
291304

292-
ReactStylesDiffMap styles = null;
293-
if (props != null) {
294-
styles = new ReactStylesDiffMap(props);
295-
cssNode.updateProperties(styles);
305+
handleCreateView(cssNode, rootViewTag, styles);
296306
}
297-
298-
handleCreateView(cssNode, rootViewTag, styles);
299307
}
300308

301309
protected void handleCreateView(
@@ -363,106 +371,108 @@ public void manageChildren(
363371
@Nullable ReadableArray addChildTags,
364372
@Nullable ReadableArray addAtIndices,
365373
@Nullable ReadableArray removeFrom) {
366-
ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag);
374+
synchronized (uiImplementationThreadLock) {
375+
ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag);
367376

368-
int numToMove = moveFrom == null ? 0 : moveFrom.size();
369-
int numToAdd = addChildTags == null ? 0 : addChildTags.size();
370-
int numToRemove = removeFrom == null ? 0 : removeFrom.size();
377+
int numToMove = moveFrom == null ? 0 : moveFrom.size();
378+
int numToAdd = addChildTags == null ? 0 : addChildTags.size();
379+
int numToRemove = removeFrom == null ? 0 : removeFrom.size();
371380

372-
if (numToMove != 0 && (moveTo == null || numToMove != moveTo.size())) {
373-
throw new IllegalViewOperationException("Size of moveFrom != size of moveTo!");
374-
}
381+
if (numToMove != 0 && (moveTo == null || numToMove != moveTo.size())) {
382+
throw new IllegalViewOperationException("Size of moveFrom != size of moveTo!");
383+
}
375384

376-
if (numToAdd != 0 && (addAtIndices == null || numToAdd != addAtIndices.size())) {
377-
throw new IllegalViewOperationException("Size of addChildTags != size of addAtIndices!");
378-
}
385+
if (numToAdd != 0 && (addAtIndices == null || numToAdd != addAtIndices.size())) {
386+
throw new IllegalViewOperationException("Size of addChildTags != size of addAtIndices!");
387+
}
379388

380-
// We treat moves as an add and a delete
381-
ViewAtIndex[] viewsToAdd = new ViewAtIndex[numToMove + numToAdd];
382-
int[] indicesToRemove = new int[numToMove + numToRemove];
383-
int[] tagsToRemove = new int[indicesToRemove.length];
384-
int[] tagsToDelete = new int[numToRemove];
385-
386-
if (numToMove > 0) {
387-
Assertions.assertNotNull(moveFrom);
388-
Assertions.assertNotNull(moveTo);
389-
for (int i = 0; i < numToMove; i++) {
390-
int moveFromIndex = moveFrom.getInt(i);
391-
int tagToMove = cssNodeToManage.getChildAt(moveFromIndex).getReactTag();
392-
viewsToAdd[i] = new ViewAtIndex(
393-
tagToMove,
394-
moveTo.getInt(i));
395-
indicesToRemove[i] = moveFromIndex;
396-
tagsToRemove[i] = tagToMove;
389+
// We treat moves as an add and a delete
390+
ViewAtIndex[] viewsToAdd = new ViewAtIndex[numToMove + numToAdd];
391+
int[] indicesToRemove = new int[numToMove + numToRemove];
392+
int[] tagsToRemove = new int[indicesToRemove.length];
393+
int[] tagsToDelete = new int[numToRemove];
394+
395+
if (numToMove > 0) {
396+
Assertions.assertNotNull(moveFrom);
397+
Assertions.assertNotNull(moveTo);
398+
for (int i = 0; i < numToMove; i++) {
399+
int moveFromIndex = moveFrom.getInt(i);
400+
int tagToMove = cssNodeToManage.getChildAt(moveFromIndex).getReactTag();
401+
viewsToAdd[i] = new ViewAtIndex(
402+
tagToMove,
403+
moveTo.getInt(i));
404+
indicesToRemove[i] = moveFromIndex;
405+
tagsToRemove[i] = tagToMove;
406+
}
397407
}
398-
}
399408

400-
if (numToAdd > 0) {
401-
Assertions.assertNotNull(addChildTags);
402-
Assertions.assertNotNull(addAtIndices);
403-
for (int i = 0; i < numToAdd; i++) {
404-
int viewTagToAdd = addChildTags.getInt(i);
405-
int indexToAddAt = addAtIndices.getInt(i);
406-
viewsToAdd[numToMove + i] = new ViewAtIndex(viewTagToAdd, indexToAddAt);
409+
if (numToAdd > 0) {
410+
Assertions.assertNotNull(addChildTags);
411+
Assertions.assertNotNull(addAtIndices);
412+
for (int i = 0; i < numToAdd; i++) {
413+
int viewTagToAdd = addChildTags.getInt(i);
414+
int indexToAddAt = addAtIndices.getInt(i);
415+
viewsToAdd[numToMove + i] = new ViewAtIndex(viewTagToAdd, indexToAddAt);
416+
}
407417
}
408-
}
409418

410-
if (numToRemove > 0) {
411-
Assertions.assertNotNull(removeFrom);
412-
for (int i = 0; i < numToRemove; i++) {
413-
int indexToRemove = removeFrom.getInt(i);
414-
int tagToRemove = cssNodeToManage.getChildAt(indexToRemove).getReactTag();
415-
indicesToRemove[numToMove + i] = indexToRemove;
416-
tagsToRemove[numToMove + i] = tagToRemove;
417-
tagsToDelete[i] = tagToRemove;
419+
if (numToRemove > 0) {
420+
Assertions.assertNotNull(removeFrom);
421+
for (int i = 0; i < numToRemove; i++) {
422+
int indexToRemove = removeFrom.getInt(i);
423+
int tagToRemove = cssNodeToManage.getChildAt(indexToRemove).getReactTag();
424+
indicesToRemove[numToMove + i] = indexToRemove;
425+
tagsToRemove[numToMove + i] = tagToRemove;
426+
tagsToDelete[i] = tagToRemove;
427+
}
418428
}
419-
}
420429

421-
// NB: moveFrom and removeFrom are both relative to the starting state of the View's children.
422-
// moveTo and addAt are both relative to the final state of the View's children.
423-
//
424-
// 1) Sort the views to add and indices to remove by index
425-
// 2) Iterate the indices being removed from high to low and remove them. Going high to low
426-
// makes sure we remove the correct index when there are multiple to remove.
427-
// 3) Iterate the views being added by index low to high and add them. Like the view removal,
428-
// iteration direction is important to preserve the correct index.
429-
430-
Arrays.sort(viewsToAdd, ViewAtIndex.COMPARATOR);
431-
Arrays.sort(indicesToRemove);
432-
433-
// Apply changes to CSSNodeDEPRECATED hierarchy
434-
int lastIndexRemoved = -1;
435-
for (int i = indicesToRemove.length - 1; i >= 0; i--) {
436-
int indexToRemove = indicesToRemove[i];
437-
if (indexToRemove == lastIndexRemoved) {
438-
throw new IllegalViewOperationException("Repeated indices in Removal list for view tag: "
439-
+ viewTag);
430+
// NB: moveFrom and removeFrom are both relative to the starting state of the View's children.
431+
// moveTo and addAt are both relative to the final state of the View's children.
432+
//
433+
// 1) Sort the views to add and indices to remove by index
434+
// 2) Iterate the indices being removed from high to low and remove them. Going high to low
435+
// makes sure we remove the correct index when there are multiple to remove.
436+
// 3) Iterate the views being added by index low to high and add them. Like the view removal,
437+
// iteration direction is important to preserve the correct index.
438+
439+
Arrays.sort(viewsToAdd, ViewAtIndex.COMPARATOR);
440+
Arrays.sort(indicesToRemove);
441+
442+
// Apply changes to CSSNodeDEPRECATED hierarchy
443+
int lastIndexRemoved = -1;
444+
for (int i = indicesToRemove.length - 1; i >= 0; i--) {
445+
int indexToRemove = indicesToRemove[i];
446+
if (indexToRemove == lastIndexRemoved) {
447+
throw new IllegalViewOperationException("Repeated indices in Removal list for view tag: "
448+
+ viewTag);
449+
}
450+
cssNodeToManage.removeChildAt(indicesToRemove[i]); // Thread safety needed here
451+
lastIndexRemoved = indicesToRemove[i];
440452
}
441-
cssNodeToManage.removeChildAt(indicesToRemove[i]);
442-
lastIndexRemoved = indicesToRemove[i];
443-
}
444453

445-
for (int i = 0; i < viewsToAdd.length; i++) {
446-
ViewAtIndex viewAtIndex = viewsToAdd[i];
447-
ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(viewAtIndex.mTag);
448-
if (cssNodeToAdd == null) {
449-
throw new IllegalViewOperationException("Trying to add unknown view tag: "
450-
+ viewAtIndex.mTag);
454+
for (int i = 0; i < viewsToAdd.length; i++) {
455+
ViewAtIndex viewAtIndex = viewsToAdd[i];
456+
ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(viewAtIndex.mTag);
457+
if (cssNodeToAdd == null) {
458+
throw new IllegalViewOperationException("Trying to add unknown view tag: "
459+
+ viewAtIndex.mTag);
460+
}
461+
cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex);
451462
}
452-
cssNodeToManage.addChildAt(cssNodeToAdd, viewAtIndex.mIndex);
453-
}
454463

455-
if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) {
456-
mNativeViewHierarchyOptimizer.handleManageChildren(
457-
cssNodeToManage,
458-
indicesToRemove,
459-
tagsToRemove,
460-
viewsToAdd,
461-
tagsToDelete);
462-
}
464+
if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) {
465+
mNativeViewHierarchyOptimizer.handleManageChildren(
466+
cssNodeToManage,
467+
indicesToRemove,
468+
tagsToRemove,
469+
viewsToAdd,
470+
tagsToDelete);
471+
}
463472

464-
for (int i = 0; i < tagsToDelete.length; i++) {
465-
removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i]));
473+
for (int i = 0; i < tagsToDelete.length; i++) {
474+
removeShadowNode(mShadowNodeRegistry.getNode(tagsToDelete[i]));
475+
}
466476
}
467477
}
468478

@@ -476,22 +486,23 @@ public void manageChildren(
476486
public void setChildren(
477487
int viewTag,
478488
ReadableArray childrenTags) {
479-
480-
ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag);
481-
482-
for (int i = 0; i < childrenTags.size(); i++) {
483-
ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(childrenTags.getInt(i));
484-
if (cssNodeToAdd == null) {
485-
throw new IllegalViewOperationException("Trying to add unknown view tag: "
486-
+ childrenTags.getInt(i));
489+
synchronized (uiImplementationThreadLock) {
490+
ReactShadowNode cssNodeToManage = mShadowNodeRegistry.getNode(viewTag);
491+
492+
for (int i = 0; i < childrenTags.size(); i++) {
493+
ReactShadowNode cssNodeToAdd = mShadowNodeRegistry.getNode(childrenTags.getInt(i));
494+
if (cssNodeToAdd == null) {
495+
throw new IllegalViewOperationException("Trying to add unknown view tag: "
496+
+ childrenTags.getInt(i));
497+
}
498+
cssNodeToManage.addChildAt(cssNodeToAdd, i);
487499
}
488-
cssNodeToManage.addChildAt(cssNodeToAdd, i);
489-
}
490500

491-
if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) {
492-
mNativeViewHierarchyOptimizer.handleSetChildren(
493-
cssNodeToManage,
494-
childrenTags);
501+
if (!cssNodeToManage.isVirtual() && !cssNodeToManage.isVirtualAnchor()) {
502+
mNativeViewHierarchyOptimizer.handleSetChildren(
503+
cssNodeToManage,
504+
childrenTags);
505+
}
495506
}
496507
}
497508

0 commit comments

Comments
 (0)