Improve RCTCxxBridge invalidation
Reviewed By: fromcelticpark Differential Revision: D5536063 fbshipit-source-id: b0f19bebea839c7da84890c338ad4d38293bc99c
This commit is contained in:
parent
023ac57337
commit
7b770556ac
|
@ -928,6 +928,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
|
|||
{
|
||||
RCTAssertJSThread();
|
||||
|
||||
if (!self.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer != nil && buffer != (id)kCFNull) {
|
||||
_wasBatchActive = YES;
|
||||
[self handleBuffer:buffer];
|
||||
|
|
|
@ -373,6 +373,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init);
|
|||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
(void)[self instance];
|
||||
RCTAssert(_methodQueue != nullptr, @"Module %@ has no methodQueue (instance: %@, bridge.valid: %d)",
|
||||
self, _instance, _bridge.valid);
|
||||
return _methodQueue;
|
||||
}
|
||||
|
||||
|
|
|
@ -149,13 +149,12 @@ struct RCTInstanceCallback : public InstanceCallback {
|
|||
[bridge_ partialBatchDidFlush];
|
||||
[bridge_ batchDidComplete];
|
||||
}
|
||||
void incrementPendingJSCalls() override {}
|
||||
void decrementPendingJSCalls() override {}
|
||||
};
|
||||
|
||||
@implementation RCTCxxBridge
|
||||
{
|
||||
BOOL _wasBatchActive;
|
||||
BOOL _didInvalidate;
|
||||
|
||||
NSMutableArray<dispatch_block_t> *_pendingCalls;
|
||||
std::atomic<NSInteger> _pendingCount;
|
||||
|
@ -194,7 +193,7 @@ struct RCTInstanceCallback : public InstanceCallback {
|
|||
|
||||
- (JSGlobalContextRef)jsContextRef
|
||||
{
|
||||
return (JSGlobalContextRef)self->_reactInstance->getJavaScriptContext();
|
||||
return (JSGlobalContextRef)(self->_reactInstance ? self->_reactInstance->getJavaScriptContext() : nullptr);
|
||||
}
|
||||
|
||||
- (instancetype)initWithParentBridge:(RCTBridge *)bridge
|
||||
|
@ -229,7 +228,7 @@ struct RCTInstanceCallback : public InstanceCallback {
|
|||
return self;
|
||||
}
|
||||
|
||||
- (void)runJSRunLoop
|
||||
+ (void)runRunLoop
|
||||
{
|
||||
@autoreleasepool {
|
||||
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge runJSRunLoop] setup", nil);
|
||||
|
@ -292,8 +291,8 @@ struct RCTInstanceCallback : public InstanceCallback {
|
|||
object:_parentBridge userInfo:@{@"bridge": self}];
|
||||
|
||||
// Set up the JS thread early
|
||||
_jsThread = [[NSThread alloc] initWithTarget:self
|
||||
selector:@selector(runJSRunLoop)
|
||||
_jsThread = [[NSThread alloc] initWithTarget:[self class]
|
||||
selector:@selector(runRunLoop)
|
||||
object:nil];
|
||||
_jsThread.name = RCTJSThreadName;
|
||||
_jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
|
||||
|
@ -522,7 +521,7 @@ struct RCTInstanceCallback : public InstanceCallback {
|
|||
|
||||
// This is async, but any calls into JS are blocked by the m_syncReady CV in Instance
|
||||
_reactInstance->initializeBridge(
|
||||
std::unique_ptr<RCTInstanceCallback>(new RCTInstanceCallback(self)),
|
||||
std::make_unique<RCTInstanceCallback>(self),
|
||||
executorFactory,
|
||||
_jsMessageThread,
|
||||
[self _buildModuleRegistry]);
|
||||
|
@ -845,6 +844,7 @@ struct RCTInstanceCallback : public InstanceCallback {
|
|||
}
|
||||
|
||||
RCTFatal(error);
|
||||
|
||||
// RN will stop, but let the rest of the app keep going.
|
||||
return;
|
||||
}
|
||||
|
@ -855,27 +855,27 @@ struct RCTInstanceCallback : public InstanceCallback {
|
|||
|
||||
// Hack: once the bridge is invalidated below, it won't initialize any new native
|
||||
// modules. Initialize the redbox module now so we can still report this error.
|
||||
[self redBox];
|
||||
RCTRedBox *redBox = [self redBox];
|
||||
|
||||
_loading = NO;
|
||||
_valid = NO;
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if (self->_jsMessageThread) {
|
||||
auto thread = self->_jsMessageThread;
|
||||
self->_jsMessageThread->runOnQueue([thread] {
|
||||
thread->quitSynchronous();
|
||||
});
|
||||
self->_jsMessageThread.reset();
|
||||
// Make sure initializeBridge completed
|
||||
self->_jsMessageThread->runOnQueueSync([] {});
|
||||
}
|
||||
|
||||
self->_reactInstance.reset();
|
||||
self->_jsMessageThread.reset();
|
||||
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
postNotificationName:RCTJavaScriptDidFailToLoadNotification
|
||||
object:self->_parentBridge userInfo:@{@"bridge": self, @"error": error}];
|
||||
|
||||
if ([error userInfo][RCTJSRawStackTraceKey]) {
|
||||
[self.redBox showErrorMessage:[error localizedDescription]
|
||||
withRawStack:[error userInfo][RCTJSRawStackTraceKey]];
|
||||
[redBox showErrorMessage:[error localizedDescription]
|
||||
withRawStack:[error userInfo][RCTJSRawStackTraceKey]];
|
||||
}
|
||||
|
||||
RCTFatal(error);
|
||||
|
@ -942,63 +942,68 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
|
|||
|
||||
- (void)invalidate
|
||||
{
|
||||
if (!_valid) {
|
||||
if (_didInvalidate) {
|
||||
return;
|
||||
}
|
||||
|
||||
RCTAssertMainQueue();
|
||||
RCTAssert(_reactInstance != nil, @"Can't complete invalidation without a react instance");
|
||||
RCTLogInfo(@"Invalidating %@ (parent: %@, executor: %@)", self, _parentBridge, [self executorClass]);
|
||||
|
||||
_loading = NO;
|
||||
_valid = NO;
|
||||
_didInvalidate = YES;
|
||||
|
||||
if ([RCTBridge currentBridge] == self) {
|
||||
[RCTBridge setCurrentBridge:nil];
|
||||
}
|
||||
|
||||
// Invalidate modules
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
for (RCTModuleData *moduleData in _moduleDataByID) {
|
||||
// Be careful when grabbing an instance here, we don't want to instantiate
|
||||
// any modules just to invalidate them.
|
||||
if (![moduleData hasInstance]) {
|
||||
continue;
|
||||
// Stop JS instance and message thread
|
||||
[self ensureOnJavaScriptThread:^{
|
||||
[self->_displayLink invalidate];
|
||||
self->_displayLink = nil;
|
||||
|
||||
if (RCTProfileIsProfiling()) {
|
||||
RCTProfileUnhookModules(self);
|
||||
}
|
||||
|
||||
if ([moduleData.instance respondsToSelector:@selector(invalidate)]) {
|
||||
dispatch_group_enter(group);
|
||||
[self dispatchBlock:^{
|
||||
[(id<RCTInvalidating>)moduleData.instance invalidate];
|
||||
dispatch_group_leave(group);
|
||||
} queue:moduleData.methodQueue];
|
||||
// Invalidate modules
|
||||
// We're on the JS thread (which we'll be suspending soon), so no new calls will be made to native modules after
|
||||
// this completes. We must ensure all previous calls were dispatched before deallocating the instance (and module
|
||||
// wrappers) or we may have invalid pointers still in flight.
|
||||
dispatch_group_t moduleInvalidation = dispatch_group_create();
|
||||
for (RCTModuleData *moduleData in self->_moduleDataByID) {
|
||||
// Be careful when grabbing an instance here, we don't want to instantiate
|
||||
// any modules just to invalidate them.
|
||||
if (![moduleData hasInstance]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([moduleData.instance respondsToSelector:@selector(invalidate)]) {
|
||||
dispatch_group_enter(moduleInvalidation);
|
||||
[self dispatchBlock:^{
|
||||
[(id<RCTInvalidating>)moduleData.instance invalidate];
|
||||
dispatch_group_leave(moduleInvalidation);
|
||||
} queue:moduleData.methodQueue];
|
||||
}
|
||||
[moduleData invalidate];
|
||||
}
|
||||
[moduleData invalidate];
|
||||
}
|
||||
|
||||
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
|
||||
[self ensureOnJavaScriptThread:^{
|
||||
[self->_displayLink invalidate];
|
||||
self->_displayLink = nil;
|
||||
if (dispatch_group_wait(moduleInvalidation, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC))) {
|
||||
RCTLogError(@"Timed out waiting for modules to be invalidated");
|
||||
}
|
||||
|
||||
self->_reactInstance.reset();
|
||||
if (self->_jsMessageThread) {
|
||||
self->_jsMessageThread->quitSynchronous();
|
||||
self->_jsMessageThread.reset();
|
||||
}
|
||||
self->_reactInstance.reset();
|
||||
self->_jsMessageThread.reset();
|
||||
|
||||
if (RCTProfileIsProfiling()) {
|
||||
RCTProfileUnhookModules(self);
|
||||
}
|
||||
self->_moduleDataByName = nil;
|
||||
self->_moduleDataByID = nil;
|
||||
self->_moduleClassesByID = nil;
|
||||
self->_pendingCalls = nil;
|
||||
|
||||
self->_moduleDataByName = nil;
|
||||
self->_moduleDataByID = nil;
|
||||
self->_moduleClassesByID = nil;
|
||||
self->_pendingCalls = nil;
|
||||
|
||||
[self->_jsThread cancel];
|
||||
self->_jsThread = nil;
|
||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||
}];
|
||||
});
|
||||
[self->_jsThread cancel];
|
||||
self->_jsThread = nil;
|
||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)logMessage:(NSString *)message level:(NSString *)level
|
||||
|
@ -1127,7 +1132,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
|
|||
*/
|
||||
|
||||
RCTProfileBeginFlowEvent();
|
||||
|
||||
[self _runAfterLoad:^{
|
||||
RCTProfileEndFlowEvent();
|
||||
|
||||
|
@ -1218,25 +1222,25 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
|
|||
if (!_reactInstance) {
|
||||
if (error) {
|
||||
*error = RCTErrorWithMessage(
|
||||
@"Attempt to call sync callFunctionOnModule: on uninitialized bridge");
|
||||
@"callFunctionOnModule was called on uninitialized bridge");
|
||||
}
|
||||
return nil;
|
||||
} else if (self.executorClass) {
|
||||
if (error) {
|
||||
*error = RCTErrorWithMessage(
|
||||
@"sync callFunctionOnModule: can only be used with JSC executor");
|
||||
@"callFunctionOnModule can only be used with JSC executor");
|
||||
}
|
||||
return nil;
|
||||
} else if (!self.valid) {
|
||||
if (error) {
|
||||
*error = RCTErrorWithMessage(
|
||||
@"sync callFunctionOnModule: bridge is no longer valid");
|
||||
@"Bridge is no longer valid");
|
||||
}
|
||||
return nil;
|
||||
} else if (self.loading) {
|
||||
if (error) {
|
||||
*error = RCTErrorWithMessage(
|
||||
@"sync callFunctionOnModule: bridge is still loading");
|
||||
@"Bridge is still loading");
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ public:
|
|||
|
||||
void runOnQueue(std::function<void()>&& func) override {
|
||||
dispatch_queue_t queue = moduleData_.methodQueue;
|
||||
RCTAssert(queue != nullptr, @"Module %@ provided invalid queue", moduleData_);
|
||||
dispatch_block_t block = [func=std::move(func)] { func(); };
|
||||
RCTAssert(block != nullptr, @"Invalid block generated in call to %@", moduleData_);
|
||||
if (queue && block) {
|
||||
|
|
|
@ -27,9 +27,9 @@ class ModuleRegistry;
|
|||
|
||||
struct InstanceCallback {
|
||||
virtual ~InstanceCallback() {}
|
||||
virtual void onBatchComplete() = 0;
|
||||
virtual void incrementPendingJSCalls() = 0;
|
||||
virtual void decrementPendingJSCalls() = 0;
|
||||
virtual void onBatchComplete() {}
|
||||
virtual void incrementPendingJSCalls() {}
|
||||
virtual void decrementPendingJSCalls() {}
|
||||
};
|
||||
|
||||
class RN_EXPORT Instance {
|
||||
|
|
Loading…
Reference in New Issue