-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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 a layout deadlock caused by holding the lock and going up the tree. #638
Changes from all commits
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 |
---|---|---|
|
@@ -211,8 +211,9 @@ @implementation ASDisplayNode (ASLayoutInternal) | |
* @discussion The size of a root node is determined by each subnode. Calling invalidateSize will let the root node know | ||
* that the intrinsic size of the receiver node is no longer valid and a resizing of the root node needs to happen. | ||
*/ | ||
- (void)_setNeedsLayoutFromAbove | ||
- (void)_u_setNeedsLayoutFromAbove | ||
{ | ||
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock); | ||
as_activity_create_for_scope("Set needs layout from above"); | ||
ASDisplayNodeAssertThreadAffinity(self); | ||
|
||
|
@@ -227,7 +228,7 @@ - (void)_setNeedsLayoutFromAbove | |
|
||
if (supernode) { | ||
// Threading model requires that we unlock before calling a method on our parent. | ||
[supernode _setNeedsLayoutFromAbove]; | ||
[supernode _u_setNeedsLayoutFromAbove]; | ||
} else { | ||
// Let the root node method know that the size was invalidated | ||
[self _rootNodeDidInvalidateSize]; | ||
|
@@ -287,8 +288,10 @@ - (void)displayNodeDidInvalidateSizeNewSize:(CGSize)size | |
} | ||
} | ||
|
||
- (void)_locked_measureNodeWithBoundsIfNecessary:(CGRect)bounds | ||
- (void)_u_measureNodeWithBoundsIfNecessary:(CGRect)bounds | ||
{ | ||
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock); | ||
ASDN::MutexLocker l(__instanceLock__); | ||
// Check if we are a subnode in a layout transition. | ||
// In this case no measurement is needed as it's part of the layout transition | ||
if ([self _isLayoutTransitionInvalid]) { | ||
|
@@ -368,8 +371,10 @@ - (void)_locked_measureNodeWithBoundsIfNecessary:(CGRect)bounds | |
// In this case, we need to detect that we've already asked to be resized to match this | ||
// particular ASLayout object, and shouldn't loop asking again unless we have a different ASLayout. | ||
nextLayout->requestedLayoutFromAbove = YES; | ||
[self _setNeedsLayoutFromAbove]; | ||
// Update the layout's version here because _setNeedsLayoutFromAbove calls __setNeedsLayout which in turn increases _layoutVersion | ||
__instanceLock__.unlock(); | ||
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. These 3 lines are the important bits which fix the deadlock. 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. @garrettmoon We do use _calculatedDisplayNodeLayout below — it looks like this would make it possible that another thread could change _calculatedDisplayNodeLayout while this is unlocked (possibly by the main thread calling -layout). I'm wondering if we should actually be returning after this, or something else like forcing a -layoutIfNeeded. It's a bit weird that we proceed to _completePendingLayoutTransition when, in actuality, we are not yet in a valid full-tree layout state until the parent has -layoutSublayers called by CA. This might be OK because one could argue that the layout is correct for this node and its children, even if not the full tree. |
||
[self _u_setNeedsLayoutFromAbove]; | ||
__instanceLock__.lock(); | ||
// Update the layout's version here because _u_setNeedsLayoutFromAbove calls __setNeedsLayout which in turn increases _layoutVersion | ||
// Failing to do this will cause the layout to be invalid immediately | ||
nextLayout->version = _layoutVersion; | ||
} | ||
|
@@ -389,7 +394,7 @@ - (void)_locked_measureNodeWithBoundsIfNecessary:(CGRect)bounds | |
|
||
- (ASSizeRange)_locked_constrainedSizeForLayoutPass | ||
{ | ||
// TODO: The logic in -_setNeedsLayoutFromAbove seems correct and doesn't use this method. | ||
// TODO: The logic in -_u_setNeedsLayoutFromAbove seems correct and doesn't use this method. | ||
// logic seems correct. For what case does -this method need to do the CGSizeEqual checks? | ||
// IF WE CAN REMOVE BOUNDS CHECKS HERE, THEN WE CAN ALSO REMOVE "REQUESTED FROM ABOVE" CHECK | ||
|
||
|
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.
Same, let's call
ASDisplayNodeAssertLockUnownedByCurrentThread()
before acquiring the lock.