Skip to content

Commit

Permalink
Fixed deadlock during app startup
Browse files Browse the repository at this point in the history
Summary:
Now that we support initializing the bridge off the main thread, some of the assumptions in the bridge setup process are no longer safe.

In particular we were assuming that the JS executor and injected modules could always be synchronously initialized within bridge init, but that is only safe if those modules don't need to be set up on the main thread.

The setup for those modules was sync-dispatching to the main thread if bridge init happened on a background thread, and this lead to a deadlock under certain circumstances.

Reviewed By: javache

Differential Revision: D3224162

fb-gh-sync-id: 7319b70f541a46ef932cfe4f776e7e192f3ce1e8
fbshipit-source-id: 7319b70f541a46ef932cfe4f776e7e192f3ce1e8
  • Loading branch information
nicklockwood authored and Facebook Github Bot 9 committed Apr 27, 2016
1 parent 6a26037 commit 9547a98
Showing 1 changed file with 32 additions and 24 deletions.
56 changes: 32 additions & 24 deletions React/Base/RCTBatchedBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,27 @@ - (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
moduleDataByName[moduleName] = moduleData;
[moduleClassesByID addObject:moduleClass];
[moduleDataByID addObject:moduleData];

// Set executor instance
if (moduleClass == self.executorClass) {
_javaScriptExecutor = (id<RCTJavaScriptExecutor>)module;
}
}

// The executor is a bridge module, but we want it to be instantiated before
// any other module has access to the bridge, in case they need the JS thread.
// TODO: once we have more fine-grained control of init (D3175632) we can
// probably just replace this with [self moduleForClass:self.executorClass]
if (!_javaScriptExecutor) {
id<RCTJavaScriptExecutor> executorModule = [self.executorClass new];
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:executorModule
bridge:self];
moduleDataByName[moduleData.name] = moduleData;
[moduleClassesByID addObject:self.executorClass];
[moduleDataByID addObject:moduleData];

// NOTE: _javaScriptExecutor is a weak reference
_javaScriptExecutor = executorModule;
}

// Set up moduleData for automatically-exported modules
Expand Down Expand Up @@ -335,17 +356,16 @@ - (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
_moduleDataByName = [moduleDataByName copy];
_moduleClassesByID = [moduleClassesByID copy];

// The executor is a bridge module, wait for it to be created and set it up
// before any other module has access to the bridge.
_javaScriptExecutor = [self moduleForClass:self.executorClass];

// Dispatch module init onto main thead for those modules that require it
// Synchronously set up the pre-initialized modules
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.hasInstance) {
// Modules that were pre-initialized need to be set up before bridge init
// has finished, otherwise the caller may try to access the module
// directly rather than via `[bridge moduleForClass:]`, which won't
// trigger the lazy initialization process.
if (moduleData.hasInstance &&
(!moduleData.requiresMainThreadSetup || [NSThread isMainThread])) {
// Modules that were pre-initialized should ideally be set up before
// bridge init has finished, otherwise the caller may try to access the
// module directly rather than via `[bridge moduleForClass:]`, which won't
// trigger the lazy initialization process. If the module cannot safely be
// set up on the current thread, it will instead be async dispatched
// to the main thread to be set up in the loop below.
(void)[moduleData instance];
}
}
Expand All @@ -359,11 +379,11 @@ - (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
NSUInteger modulesOnMainThreadCount = 0;
for (RCTModuleData *moduleData in _moduleDataByID) {
__weak RCTBatchedBridge *weakSelf = self;
if (moduleData.requiresMainThreadSetup) {
if (moduleData.requiresMainThreadSetup || moduleData.hasConstantsToExport) {
// Modules that need to be set up on the main thread cannot be initialized
// lazily when required without doing a dispatch_sync to the main thread,
// which can result in deadlock. To avoid this, we initialize all of these
// modules on the main thread in parallel with loading the JS code, so that
// modules on the main thread in parallel with loading the JS code, so
// they will already be available before they are ever required.
dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), ^{
if (weakSelf.valid) {
Expand All @@ -374,18 +394,6 @@ - (void)initModulesWithDispatchGroup:(dispatch_group_t)dispatchGroup
}
});
modulesOnMainThreadCount++;
} else if (moduleData.hasConstantsToExport) {
// Constants must be exported on the main thread, but module setup can
// be done on any queue
(void)[moduleData instance];
dispatch_group_async(dispatchGroup, dispatch_get_main_queue(), ^{
if (weakSelf.valid) {
RCTPerformanceLoggerAppendStart(RCTPLNativeModuleMainThread);
[moduleData gatherConstants];
RCTPerformanceLoggerAppendEnd(RCTPLNativeModuleMainThread);
}
});
modulesOnMainThreadCount++;
}
}

Expand Down

0 comments on commit 9547a98

Please sign in to comment.