iOS: prevent nativemodule access from JS if bridge is no longer valid
Summary: This helps prevent race condition where JS calls to NativeModules got queued and executed while the bridge is invalidating itself, causing assertion failures in test setup (for example). It won't prevent it 100% of the time, due to threading (and adding lock is expensive for each nativemodule call). Reviewed By: yungsters Differential Revision: D9231636 fbshipit-source-id: 298eaf52ffa4b84108184124e75b206b9ca7a41d
This commit is contained in:
parent
e6b305b722
commit
29245e96cb
|
@ -115,20 +115,20 @@ expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
|
||||||
{
|
{
|
||||||
__weak RCTBridge *batchedBridge;
|
__weak RCTBridge *batchedBridge;
|
||||||
NSNumber *rootTag;
|
NSNumber *rootTag;
|
||||||
|
RCTLogFunction defaultLogFunction = RCTGetLogFunction();
|
||||||
|
// Catch all error logs, that are equivalent to redboxes in dev mode.
|
||||||
|
__block NSMutableArray<NSString *> *errors = nil;
|
||||||
|
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
|
||||||
|
defaultLogFunction(level, source, fileName, lineNumber, message);
|
||||||
|
if (level >= RCTLogLevelError) {
|
||||||
|
if (errors == nil) {
|
||||||
|
errors = [NSMutableArray new];
|
||||||
|
}
|
||||||
|
[errors addObject:message];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
__block NSMutableArray<NSString *> *errors = nil;
|
|
||||||
RCTLogFunction defaultLogFunction = RCTGetLogFunction();
|
|
||||||
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
|
|
||||||
defaultLogFunction(level, source, fileName, lineNumber, message);
|
|
||||||
if (level >= RCTLogLevelError) {
|
|
||||||
if (errors == nil) {
|
|
||||||
errors = [NSMutableArray new];
|
|
||||||
}
|
|
||||||
[errors addObject:message];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL
|
RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:_scriptURL
|
||||||
moduleProvider:_moduleProvider
|
moduleProvider:_moduleProvider
|
||||||
launchOptions:nil];
|
launchOptions:nil];
|
||||||
|
@ -172,7 +172,16 @@ expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
|
||||||
testModule.view = nil;
|
testModule.view = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
RCTSetLogFunction(defaultLogFunction);
|
// From this point on catch only fatal errors.
|
||||||
|
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
|
||||||
|
defaultLogFunction(level, source, fileName, lineNumber, message);
|
||||||
|
if (level >= RCTLogLevelFatal) {
|
||||||
|
if (errors == nil) {
|
||||||
|
errors = [NSMutableArray new];
|
||||||
|
}
|
||||||
|
[errors addObject:message];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
#if RCT_DEV
|
#if RCT_DEV
|
||||||
NSArray<UIView *> *nonLayoutSubviews = [vc.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id subview, NSDictionary *bindings) {
|
NSArray<UIView *> *nonLayoutSubviews = [vc.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id subview, NSDictionary *bindings) {
|
||||||
|
@ -208,8 +217,10 @@ expectErrorBlock:(BOOL(^)(NSString *error))expectErrorBlock
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
||||||
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
||||||
}
|
}
|
||||||
// Note: this deallocation isn't consistently working in test setup, so disable the assertion.
|
RCTAssert(errors == nil, @"RedBox errors during bridge invalidation: %@", errors);
|
||||||
// RCTAssert(batchedBridge == nil, @"Bridge should be deallocated after the test");
|
RCTAssert(batchedBridge == nil, @"Bridge should be deallocated after the test");
|
||||||
|
|
||||||
|
RCTSetLogFunction(defaultLogFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -71,11 +71,16 @@ void RCTNativeModule::invoke(unsigned int methodId, folly::dynamic &¶ms, int
|
||||||
invokeInner(weakBridge, weakModuleData, methodId, std::move(params));
|
invokeInner(weakBridge, weakModuleData, methodId, std::move(params));
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch_queue_t queue = m_moduleData.methodQueue;
|
if (m_bridge.valid) {
|
||||||
if (queue == RCTJSThread) {
|
dispatch_queue_t queue = m_moduleData.methodQueue;
|
||||||
block();
|
if (queue == RCTJSThread) {
|
||||||
} else if (queue) {
|
block();
|
||||||
dispatch_async(queue, block);
|
} else if (queue) {
|
||||||
|
dispatch_async(queue, block);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RCTLogError(@"Attempted to invoke `%u` (method ID) on `%@` (NativeModule name) with an invalid bridge.",
|
||||||
|
methodId, m_moduleData.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue