mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Fabric: Fixing a deadlock in RCTSurfacePresenter
Summary: This is another attempt to fix an issue very similar to T59424871. The previous attempt was in D19249490. I don't know why we don't see production crashes (stalls) but it happened to me (and not to me) in the debugger. The previous attempt didn't work because we still could have a deadlock because we tried to acquired shared mutex already owned exclusively by the `suspend` method. Here is another approach: Instead of using one shared mutex, now we use two. One is similar to what we had and another that protects `suspend` and `resume`. Besides that, now we pass a Scheduler to functions that use that explicitly. This way we can be more explicit about acquiring mutexes and the overall flow of execution. The idea is: Now an arbitrary code that can be reentrant does not cover with mutexes, so the deadlock is not possible. Changelog: [Internal] Fabric-specific internal change. Reviewed By: sammy-SC Differential Revision: D20639228 fbshipit-source-id: 98515742f00f2ae94b50b585c9f1f0611e169ebe
This commit is contained in:
committed by
Facebook GitHub Bot
parent
89d04b5e4a
commit
73448012cb
@@ -44,10 +44,11 @@ using namespace facebook::react;
|
||||
RCTMountingManager *_mountingManager; // Thread-safe.
|
||||
RCTSurfaceRegistry *_surfaceRegistry; // Thread-safe.
|
||||
|
||||
better::shared_mutex _schedulerMutex;
|
||||
RCTScheduler *_Nullable _scheduler; // Thread-safe. Pointer is protected by `_schedulerMutex`.
|
||||
ContextContainer::Shared _contextContainer; // Protected by `_schedulerMutex`.
|
||||
RuntimeExecutor _runtimeExecutor; // Protected by `_schedulerMutex`.
|
||||
std::mutex _schedulerAccessMutex;
|
||||
std::mutex _schedulerLifeCycleMutex;
|
||||
RCTScheduler *_Nullable _scheduler; // Thread-safe. Pointer is protected by `_schedulerAccessMutex`.
|
||||
ContextContainer::Shared _contextContainer; // Protected by `_schedulerLifeCycleMutex`.
|
||||
RuntimeExecutor _runtimeExecutor; // Protected by `_schedulerLifeCycleMutex`.
|
||||
|
||||
better::shared_mutex _observerListMutex;
|
||||
NSMutableArray<id<RCTSurfacePresenterObserver>> *_observers;
|
||||
@@ -74,27 +75,33 @@ using namespace facebook::react;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (RCTScheduler *_Nullable)_scheduler
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_schedulerAccessMutex);
|
||||
return _scheduler;
|
||||
}
|
||||
|
||||
- (ContextContainer::Shared)contextContainer
|
||||
{
|
||||
std::shared_lock<better::shared_mutex> lock(_schedulerMutex);
|
||||
std::lock_guard<std::mutex> lock(_schedulerLifeCycleMutex);
|
||||
return _contextContainer;
|
||||
}
|
||||
|
||||
- (void)setContextContainer:(ContextContainer::Shared)contextContainer
|
||||
{
|
||||
std::unique_lock<better::shared_mutex> lock(_schedulerMutex);
|
||||
std::lock_guard<std::mutex> lock(_schedulerLifeCycleMutex);
|
||||
_contextContainer = contextContainer;
|
||||
}
|
||||
|
||||
- (RuntimeExecutor)runtimeExecutor
|
||||
{
|
||||
std::shared_lock<better::shared_mutex> lock(_schedulerMutex);
|
||||
std::lock_guard<std::mutex> lock(_schedulerLifeCycleMutex);
|
||||
return _runtimeExecutor;
|
||||
}
|
||||
|
||||
- (void)setRuntimeExecutor:(RuntimeExecutor)runtimeExecutor
|
||||
{
|
||||
std::unique_lock<better::shared_mutex> lock(_schedulerMutex);
|
||||
std::lock_guard<std::mutex> lock(_schedulerLifeCycleMutex);
|
||||
_runtimeExecutor = runtimeExecutor;
|
||||
}
|
||||
|
||||
@@ -102,28 +109,29 @@ using namespace facebook::react;
|
||||
|
||||
- (void)registerSurface:(RCTFabricSurface *)surface
|
||||
{
|
||||
std::shared_lock<better::shared_mutex> lock(_schedulerMutex);
|
||||
RCTScheduler *scheduler = [self _scheduler];
|
||||
[_surfaceRegistry registerSurface:surface];
|
||||
if (_scheduler) {
|
||||
[self _startSurface:surface];
|
||||
if (scheduler) {
|
||||
[self _startSurface:surface scheduler:scheduler];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)unregisterSurface:(RCTFabricSurface *)surface
|
||||
{
|
||||
std::shared_lock<better::shared_mutex> lock(_schedulerMutex);
|
||||
if (_scheduler) {
|
||||
[self _stopSurface:surface];
|
||||
RCTScheduler *scheduler = [self _scheduler];
|
||||
if (scheduler) {
|
||||
[self _stopSurface:surface scheduler:scheduler];
|
||||
}
|
||||
[_surfaceRegistry unregisterSurface:surface];
|
||||
}
|
||||
|
||||
- (void)setProps:(NSDictionary *)props surface:(RCTFabricSurface *)surface
|
||||
{
|
||||
std::shared_lock<better::shared_mutex> lock(_schedulerMutex);
|
||||
// This implementation is suboptimal indeed but still better than nothing for now.
|
||||
[self _stopSurface:surface];
|
||||
[self _startSurface:surface];
|
||||
RCTScheduler *scheduler = [self _scheduler];
|
||||
if (scheduler) {
|
||||
[self _stopSurface:surface scheduler:scheduler];
|
||||
[self _startSurface:surface scheduler:scheduler];
|
||||
}
|
||||
}
|
||||
|
||||
- (RCTFabricSurface *)surfaceForRootTag:(ReactTag)rootTag
|
||||
@@ -135,31 +143,42 @@ using namespace facebook::react;
|
||||
maximumSize:(CGSize)maximumSize
|
||||
surface:(RCTFabricSurface *)surface
|
||||
{
|
||||
std::shared_lock<better::shared_mutex> lock(_schedulerMutex);
|
||||
RCTScheduler *scheduler = [self _scheduler];
|
||||
if (!scheduler) {
|
||||
return minimumSize;
|
||||
}
|
||||
LayoutContext layoutContext = {.pointScaleFactor = RCTScreenScale()};
|
||||
LayoutConstraints layoutConstraints = {.minimumSize = RCTSizeFromCGSize(minimumSize),
|
||||
.maximumSize = RCTSizeFromCGSize(maximumSize),
|
||||
.layoutDirection = RCTLayoutDirection([[RCTI18nUtil sharedInstance] isRTL])};
|
||||
return [_scheduler measureSurfaceWithLayoutConstraints:layoutConstraints
|
||||
layoutContext:layoutContext
|
||||
surfaceId:surface.rootTag];
|
||||
return [scheduler measureSurfaceWithLayoutConstraints:layoutConstraints
|
||||
layoutContext:layoutContext
|
||||
surfaceId:surface.rootTag];
|
||||
}
|
||||
|
||||
- (void)setMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize surface:(RCTFabricSurface *)surface
|
||||
{
|
||||
std::shared_lock<better::shared_mutex> lock(_schedulerMutex);
|
||||
RCTScheduler *scheduler = [self _scheduler];
|
||||
if (!scheduler) {
|
||||
return;
|
||||
}
|
||||
|
||||
LayoutContext layoutContext = {.pointScaleFactor = RCTScreenScale()};
|
||||
LayoutConstraints layoutConstraints = {.minimumSize = RCTSizeFromCGSize(minimumSize),
|
||||
.maximumSize = RCTSizeFromCGSize(maximumSize),
|
||||
.layoutDirection = RCTLayoutDirection([[RCTI18nUtil sharedInstance] isRTL])};
|
||||
[_scheduler constraintSurfaceLayoutWithLayoutConstraints:layoutConstraints
|
||||
layoutContext:layoutContext
|
||||
surfaceId:surface.rootTag];
|
||||
[scheduler constraintSurfaceLayoutWithLayoutConstraints:layoutConstraints
|
||||
layoutContext:layoutContext
|
||||
surfaceId:surface.rootTag];
|
||||
}
|
||||
|
||||
- (BOOL)synchronouslyUpdateViewOnUIThread:(NSNumber *)reactTag props:(NSDictionary *)props
|
||||
{
|
||||
std::shared_lock<better::shared_mutex> lock(_schedulerMutex);
|
||||
RCTScheduler *scheduler = [self _scheduler];
|
||||
if (!scheduler) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
ReactTag tag = [reactTag integerValue];
|
||||
UIView<RCTComponentViewProtocol> *componentView =
|
||||
[_mountingManager.componentViewRegistry findComponentViewWithTag:tag];
|
||||
@@ -167,7 +186,7 @@ using namespace facebook::react;
|
||||
return NO; // This view probably isn't managed by Fabric
|
||||
}
|
||||
ComponentHandle handle = [[componentView class] componentDescriptorProvider].handle;
|
||||
auto *componentDescriptor = [_scheduler findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN:handle];
|
||||
auto *componentDescriptor = [scheduler findComponentDescriptorByHandle_DO_NOT_USE_THIS_IS_BROKEN:handle];
|
||||
|
||||
if (!componentDescriptor) {
|
||||
return YES;
|
||||
@@ -179,7 +198,12 @@ using namespace facebook::react;
|
||||
|
||||
- (BOOL)synchronouslyWaitSurface:(RCTFabricSurface *)surface timeout:(NSTimeInterval)timeout
|
||||
{
|
||||
auto mountingCoordinator = [_scheduler mountingCoordinatorWithSurfaceId:surface.rootTag];
|
||||
RCTScheduler *scheduler = [self _scheduler];
|
||||
if (!scheduler) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
auto mountingCoordinator = [scheduler mountingCoordinatorWithSurfaceId:surface.rootTag];
|
||||
|
||||
if (!mountingCoordinator->waitForTransaction(std::chrono::duration<NSTimeInterval>(timeout))) {
|
||||
return NO;
|
||||
@@ -192,28 +216,44 @@ using namespace facebook::react;
|
||||
|
||||
- (BOOL)suspend
|
||||
{
|
||||
std::unique_lock<better::shared_mutex> lock(_schedulerMutex);
|
||||
std::lock_guard<std::mutex> lock(_schedulerLifeCycleMutex);
|
||||
|
||||
if (!_scheduler) {
|
||||
return NO;
|
||||
RCTScheduler *scheduler;
|
||||
{
|
||||
std::lock_guard<std::mutex> accessLock(_schedulerAccessMutex);
|
||||
|
||||
if (!_scheduler) {
|
||||
return NO;
|
||||
}
|
||||
scheduler = _scheduler;
|
||||
_scheduler = nil;
|
||||
}
|
||||
|
||||
[self _stopAllSurfaces];
|
||||
_scheduler = nil;
|
||||
[self _stopAllSurfacesWithScheduler:scheduler];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)resume
|
||||
{
|
||||
std::unique_lock<better::shared_mutex> lock(_schedulerMutex);
|
||||
std::lock_guard<std::mutex> lock(_schedulerLifeCycleMutex);
|
||||
|
||||
if (_scheduler) {
|
||||
return NO;
|
||||
RCTScheduler *scheduler;
|
||||
{
|
||||
std::lock_guard<std::mutex> accessLock(_schedulerAccessMutex);
|
||||
|
||||
if (_scheduler) {
|
||||
return NO;
|
||||
}
|
||||
scheduler = [self _createScheduler];
|
||||
}
|
||||
|
||||
_scheduler = [self _createScheduler];
|
||||
[self _startAllSurfaces];
|
||||
[self _startAllSurfacesWithScheduler:scheduler];
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> accessLock(_schedulerAccessMutex);
|
||||
_scheduler = scheduler;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
@@ -250,7 +290,7 @@ using namespace facebook::react;
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
- (void)_startSurface:(RCTFabricSurface *)surface
|
||||
- (void)_startSurface:(RCTFabricSurface *)surface scheduler:(RCTScheduler *)scheduler
|
||||
{
|
||||
RCTMountingManager *mountingManager = _mountingManager;
|
||||
RCTExecuteOnMainQueue(^{
|
||||
@@ -264,16 +304,16 @@ using namespace facebook::react;
|
||||
.maximumSize = RCTSizeFromCGSize(surface.maximumSize),
|
||||
.layoutDirection = RCTLayoutDirection([[RCTI18nUtil sharedInstance] isRTL])};
|
||||
|
||||
[_scheduler startSurfaceWithSurfaceId:surface.rootTag
|
||||
moduleName:surface.moduleName
|
||||
initialProps:surface.properties
|
||||
layoutConstraints:layoutConstraints
|
||||
layoutContext:layoutContext];
|
||||
[scheduler startSurfaceWithSurfaceId:surface.rootTag
|
||||
moduleName:surface.moduleName
|
||||
initialProps:surface.properties
|
||||
layoutConstraints:layoutConstraints
|
||||
layoutContext:layoutContext];
|
||||
}
|
||||
|
||||
- (void)_stopSurface:(RCTFabricSurface *)surface
|
||||
- (void)_stopSurface:(RCTFabricSurface *)surface scheduler:(RCTScheduler *)scheduler
|
||||
{
|
||||
[_scheduler stopSurfaceWithSurfaceId:surface.rootTag];
|
||||
[scheduler stopSurfaceWithSurfaceId:surface.rootTag];
|
||||
|
||||
RCTMountingManager *mountingManager = _mountingManager;
|
||||
RCTExecuteOnMainQueue(^{
|
||||
@@ -287,20 +327,20 @@ using namespace facebook::react;
|
||||
[surface _unsetStage:(RCTSurfaceStagePrepared | RCTSurfaceStageMounted)];
|
||||
}
|
||||
|
||||
- (void)_startAllSurfaces
|
||||
- (void)_startAllSurfacesWithScheduler:(RCTScheduler *)scheduler
|
||||
{
|
||||
[_surfaceRegistry enumerateWithBlock:^(NSEnumerator<RCTFabricSurface *> *enumerator) {
|
||||
for (RCTFabricSurface *surface in enumerator) {
|
||||
[self _startSurface:surface];
|
||||
[self _startSurface:surface scheduler:scheduler];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_stopAllSurfaces
|
||||
- (void)_stopAllSurfacesWithScheduler:(RCTScheduler *)scheduler
|
||||
{
|
||||
[_surfaceRegistry enumerateWithBlock:^(NSEnumerator<RCTFabricSurface *> *enumerator) {
|
||||
for (RCTFabricSurface *surface in enumerator) {
|
||||
[self _stopSurface:surface];
|
||||
[self _stopSurface:surface scheduler:scheduler];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user