-
-
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 issue causing ANR when performing sync layout updates #4239
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 |
---|---|---|
|
@@ -96,6 +96,8 @@ @implementation REANodesManager { | |
BOOL _tryRunBatchUpdatesSynchronously; | ||
REAEventHandler _eventHandler; | ||
volatile void (^_mounting)(void); | ||
NSObject *_syncLayoutUpdatesWaitLock; | ||
volatile BOOL _syncLayoutUpdatesWaitTimedOut; | ||
NSMutableDictionary<NSNumber *, ComponentUpdate *> *_componentUpdateBuffer; | ||
NSMutableDictionary<NSNumber *, UIView *> *_viewRegistry; | ||
#ifdef RCT_NEW_ARCH_ENABLED | ||
|
@@ -123,6 +125,7 @@ - (nonnull instancetype)initWithModule:(REAModule *)reanimatedModule | |
_operationsInBatch = [NSMutableDictionary new]; | ||
_componentUpdateBuffer = [NSMutableDictionary new]; | ||
_viewRegistry = [_uiManager valueForKey:@"_viewRegistry"]; | ||
_syncLayoutUpdatesWaitLock = [NSObject new]; | ||
} | ||
|
||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onAnimationFrame:)]; | ||
|
@@ -241,8 +244,14 @@ - (void)onAnimationFrame:(CADisplayLink *)displayLink | |
- (BOOL)uiManager:(RCTUIManager *)manager performMountingWithBlock:(RCTUIManagerMountingBlock)block | ||
{ | ||
RCTAssert(_mounting == nil, @"Mouting block is expected to not be set"); | ||
_mounting = block; | ||
return YES; | ||
@synchronized(_syncLayoutUpdatesWaitLock) { | ||
if (_syncLayoutUpdatesWaitTimedOut) { | ||
return NO; | ||
} else { | ||
_mounting = block; | ||
return YES; | ||
} | ||
} | ||
} | ||
|
||
- (void)performOperations | ||
|
@@ -260,6 +269,7 @@ - (void)performOperations | |
|
||
__weak __typeof__(self) weakSelf = self; | ||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); | ||
_syncLayoutUpdatesWaitTimedOut = NO; | ||
RCTExecuteOnUIManagerQueue(^{ | ||
__typeof__(self) strongSelf = weakSelf; | ||
if (strongSelf == nil) { | ||
|
@@ -276,15 +286,24 @@ - (void)performOperations | |
} | ||
|
||
if (canUpdateSynchronously) { | ||
[strongSelf.uiManager runSyncUIUpdatesWithObserver:self]; | ||
[strongSelf.uiManager runSyncUIUpdatesWithObserver:strongSelf]; | ||
dispatch_semaphore_signal(semaphore); | ||
} | ||
// In case canUpdateSynchronously=true we still have to send uiManagerWillPerformMounting event | ||
// to observers because some components (e.g. TextInput) update their UIViews only on that event. | ||
[strongSelf.uiManager setNeedsLayout]; | ||
}); | ||
if (trySynchronously) { | ||
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); | ||
// The 16ms timeout here aims to match the frame duration. It may make sense to read that parameter | ||
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. What about 120 fps mode? 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. Was trying to keep this change minimal as may want to port it back to Rea 2. We could pass expected frame duration here but we don't always have it available (e.g. we have it when we run operations from CADisplayLink callback but when handling events display link instance could be paused). In my opinion the consequences of this timeout being longer than one frame on 120fps devices isn't very serious. This deadlock happens very randomly and not that often. If the timeout takes two frames we will just drop one extra frame on such device. |
||
// from CADisplayLink but it is easier to hardcode it for the time being. | ||
// The reason why we use frame duration here is that if takes longer than one frame to complete layout tasks | ||
// there is no point of synchronizing layout with the UI interaction as we get that one frame delay anyways. | ||
long result = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 16 * NSEC_PER_MSEC)); | ||
if (result != 0) { | ||
@synchronized(_syncLayoutUpdatesWaitLock) { | ||
_syncLayoutUpdatesWaitTimedOut = YES; | ||
} | ||
} | ||
} | ||
|
||
if (_mounting) { | ||
|
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.
_syncLayoutUpdatesWaitTimedOut
- just for sure.volatile BOOL + lock
would be better approach here.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.
_mounting
property also happens inside of the synchronized block and has to be synchronized for this to work. Using atomic bool won't suffice