-
Notifications
You must be signed in to change notification settings - Fork 764
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix two race conditions in RecyclerBinder. #917
base: master
Are you sure you want to change the base?
Changes from all commits
44b53f6
cb5a551
c217b6a
32494a8
bacb83a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,8 @@ | |
import com.facebook.litho.ComponentContext; | ||
import com.facebook.litho.ComponentTree; | ||
import com.facebook.litho.ComponentTree.MeasureListener; | ||
import com.facebook.litho.ComponentTree.WorkingRangeAndPositionHolder; | ||
import com.facebook.litho.ComponentTree.WorkingRangeHolder; | ||
import com.facebook.litho.ErrorEventHandler; | ||
import com.facebook.litho.LithoLifecycleListener; | ||
import com.facebook.litho.LithoLifecycleProvider; | ||
|
@@ -116,6 +118,9 @@ interface ComponentTreeMeasureListenerFactory { | |
@GuardedBy("this") | ||
private int mLastRequestedHeightSpec = UNINITIALIZED; | ||
|
||
@GuardedBy("this") | ||
private WorkingRangeAndPositionHolder mPendingWorkingRangeAndPosition = null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add @nullable to this field to make it Nullsafe? |
||
|
||
public static Builder create() { | ||
return new Builder(); | ||
} | ||
|
@@ -426,19 +431,30 @@ synchronized void setMeasuredHeight(int height) { | |
mLastMeasuredHeight = height; | ||
} | ||
|
||
synchronized void checkWorkingRangeAndDispatch( | ||
void checkWorkingRangeAndDispatch( | ||
int position, | ||
int firstVisibleIndex, | ||
int lastVisibleIndex, | ||
int firstFullyVisibleIndex, | ||
int lastFullyVisibleIndex) { | ||
if (mComponentTree != null) { | ||
mComponentTree.checkWorkingRangeAndDispatch( | ||
position, | ||
firstVisibleIndex, | ||
lastVisibleIndex, | ||
firstFullyVisibleIndex, | ||
lastFullyVisibleIndex); | ||
checkWorkingRangeAndDispatch( | ||
new WorkingRangeAndPositionHolder( | ||
position, | ||
new WorkingRangeHolder( | ||
firstVisibleIndex, | ||
lastVisibleIndex, | ||
firstFullyVisibleIndex, | ||
lastFullyVisibleIndex))); | ||
} | ||
|
||
synchronized void checkWorkingRangeAndDispatch( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
WorkingRangeAndPositionHolder workingRangeAndPosition) { | ||
if (mComponentTree == null) { | ||
if (ComponentsConfiguration.useReliableWorkingRange) { | ||
mPendingWorkingRangeAndPosition = workingRangeAndPosition; | ||
} | ||
} else { | ||
mComponentTree.checkWorkingRangeAndDispatch(workingRangeAndPosition); | ||
} | ||
} | ||
|
||
|
@@ -503,6 +519,10 @@ private void ensureComponentTree(ComponentContext context) { | |
if (mPendingNewLayoutListener != null) { | ||
mComponentTree.setNewLayoutStateReadyListener(mPendingNewLayoutListener); | ||
} | ||
if (mPendingWorkingRangeAndPosition != null) { | ||
mComponentTree.checkWorkingRangeAndDispatch(mPendingWorkingRangeAndPosition); | ||
mPendingWorkingRangeAndPosition = null; | ||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,6 +52,8 @@ | |
import com.facebook.litho.ComponentLogParams; | ||
import com.facebook.litho.ComponentTree; | ||
import com.facebook.litho.ComponentTree.MeasureListener; | ||
import com.facebook.litho.ComponentTree.WorkingRangeAndPositionHolder; | ||
import com.facebook.litho.ComponentTree.WorkingRangeHolder; | ||
import com.facebook.litho.ComponentUtils; | ||
import com.facebook.litho.ComponentsLogger; | ||
import com.facebook.litho.ComponentsReporter; | ||
|
@@ -221,6 +223,8 @@ public void run() { | |
private final RunnableHandler mPreallocateMountContentHandler; | ||
private final boolean mPreallocatePerMountSpec; | ||
|
||
private @Nullable WorkingRangeHolder mPendingWorkingRange; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This private field is never accessed, can you clarify its purpose? Thanks! |
||
|
||
private MeasureListener getMeasureListener(final ComponentTreeHolder holder) { | ||
return new MeasureListener() { | ||
@Override | ||
|
@@ -1184,7 +1188,8 @@ private boolean isRecyclerViewTargetComputingLayout() { | |
public void setSubAdapterModeRecyclerView(RecyclerView recyclerView) { | ||
if (!mIsSubAdapter) { | ||
throw new IllegalStateException( | ||
"Cannot set a subadapter RecyclerView on a RecyclerBinder which is not in subadapter mode."); | ||
"Cannot set a subadapter RecyclerView on a RecyclerBinder which is not in subadapter" | ||
+ " mode."); | ||
} | ||
|
||
registerDrawListener(recyclerView); | ||
|
@@ -1195,7 +1200,8 @@ public void setSubAdapterModeRecyclerView(RecyclerView recyclerView) { | |
public void removeSubAdapterModeRecyclerView(RecyclerView recyclerView) { | ||
if (!mIsSubAdapter) { | ||
throw new IllegalStateException( | ||
"Cannot remove a subadapter RecyclerView on a RecyclerBinder which is not in subadapter mode."); | ||
"Cannot remove a subadapter RecyclerView on a RecyclerBinder which is not in subadapter" | ||
+ " mode."); | ||
} | ||
|
||
unregisterDrawListener(recyclerView); | ||
|
@@ -2335,7 +2341,8 @@ public void measure( | |
shouldMeasureItemForSize(widthSpec, heightSpec, scrollDirection, canRemeasure); | ||
if (mHasManualEstimatedViewportCount && shouldMeasureItemForSize) { | ||
throw new RuntimeException( | ||
"Cannot use manual estimated viewport count when the RecyclerBinder needs an item to determine its size!"); | ||
"Cannot use manual estimated viewport count when the RecyclerBinder needs an item to" | ||
+ " determine its size!"); | ||
} | ||
|
||
mIsInMeasure.set(true); | ||
|
@@ -3330,26 +3337,36 @@ void onNewWorkingRange( | |
int lastVisibleIndex, | ||
int firstFullyVisibleIndex, | ||
int lastFullyVisibleIndex) { | ||
onNewWorkingRange( | ||
new WorkingRangeHolder( | ||
firstVisibleIndex, lastVisibleIndex, firstFullyVisibleIndex, lastFullyVisibleIndex)); | ||
} | ||
|
||
private void onNewWorkingRange(WorkingRangeHolder workingRange) { | ||
if (mEstimatedViewportCount == UNSET | ||
|| firstVisibleIndex == RecyclerView.NO_POSITION | ||
|| lastVisibleIndex == RecyclerView.NO_POSITION) { | ||
|| workingRange.firstVisibleIndex == RecyclerView.NO_POSITION | ||
|| workingRange.lastVisibleIndex == RecyclerView.NO_POSITION) { | ||
if (ComponentsConfiguration.useReliableWorkingRange) { | ||
mPendingWorkingRange = workingRange; | ||
} | ||
return; | ||
} | ||
|
||
final int rangeSize = Math.max(mEstimatedViewportCount, lastVisibleIndex - firstVisibleIndex); | ||
final int rangeSize = | ||
Math.max( | ||
mEstimatedViewportCount, | ||
workingRange.lastVisibleIndex - workingRange.firstVisibleIndex); | ||
final int layoutRangeSize = (int) (rangeSize * mRangeRatio); | ||
final int rangeStart = Math.max(0, firstVisibleIndex - layoutRangeSize); | ||
final int rangeStart = Math.max(0, workingRange.firstVisibleIndex - layoutRangeSize); | ||
final int rangeEnd = | ||
Math.min(firstVisibleIndex + rangeSize + layoutRangeSize, mComponentTreeHolders.size() - 1); | ||
Math.min( | ||
workingRange.firstVisibleIndex + rangeSize + layoutRangeSize, | ||
mComponentTreeHolders.size() - 1); | ||
|
||
for (int position = rangeStart; position <= rangeEnd; position++) { | ||
final ComponentTreeHolder holder = mComponentTreeHolders.get(position); | ||
holder.checkWorkingRangeAndDispatch( | ||
position, | ||
firstVisibleIndex, | ||
lastVisibleIndex, | ||
firstFullyVisibleIndex, | ||
lastFullyVisibleIndex); | ||
new WorkingRangeAndPositionHolder(position, workingRange)); | ||
} | ||
} | ||
|
||
|
@@ -3373,7 +3390,15 @@ private void computeRange(int firstVisible, int lastVisible, RecyclerRangeTraver | |
final boolean didRangeExtremitiesChange; | ||
|
||
synchronized (this) { | ||
if (!isMeasured() || mEstimatedViewportCount == UNSET) { | ||
// When mHasDynamicItemHeight is set, isMeasured() always returns false, to trigger re-measure | ||
// of the RecyclerView whenever a new child item is measured. However, this causes this | ||
// function to return early, preventing layout ranges from being applied. | ||
boolean isMeasured = | ||
ComponentsConfiguration.useReliableWorkingRange && mHasDynamicItemHeight | ||
? mIsMeasured.get() | ||
: isMeasured(); | ||
|
||
if (!isMeasured || mEstimatedViewportCount == UNSET) { | ||
return; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This lock seems unnecessary, because
commitResolutionResult
is synchronized. Can we remove it?