TM iOS: force flush message queue when calling into JS from native

Summary: When calling into JS (e.g. promise resolve/reject, callback) in TurboModule, we bypass the bridge's message queue. At times this causes race condition, where there are a bunch of pending UI operations (in RCTUImanager) waiting to be flushed, but nothing adds calls to the message queue. Usually tapping the screen will trigger the flush because we're sending down touch events to JS.

Reviewed By: JoshuaGross

Differential Revision: D14656466

fbshipit-source-id: cb3a174e97542bf80f0a37b4170b6a8e6780fa35
This commit is contained in:
Kevin Gozali
2019-03-29 01:31:31 -07:00
committed by Facebook Github Bot
parent 1592acd4a9
commit a43e666a34
10 changed files with 37 additions and 16 deletions

View File

@@ -210,6 +210,11 @@ struct RCTInstanceCallback : public InstanceCallback {
return _jsMessageThread;
}
- (std::shared_ptr<Instance>)reactInstance
{
return _reactInstance;
}
- (BOOL)isInspectable
{
return _reactInstance ? _reactInstance->isInspectable() : NO;

View File

@@ -180,5 +180,12 @@ void Instance::handleMemoryPressure(int pressureLevel) {
nativeToJsBridge_->handleMemoryPressure(pressureLevel);
}
void Instance::invokeAsync(std::function<void()>&& func) {
nativeToJsBridge_->runOnExecutorQueue([func=std::move(func)](JSExecutor *executor) {
func();
executor->flush();
});
}
} // namespace react
} // namespace facebook

View File

@@ -71,6 +71,8 @@ public:
void handleMemoryPressure(int pressureLevel);
void invokeAsync(std::function<void()>&& func);
private:
void callNativeModules(folly::dynamic &&calls, bool isEndOfBatch);
void loadApplication(std::unique_ptr<RAMBundleRegistry> bundleRegistry,

View File

@@ -108,6 +108,8 @@ public:
virtual void destroy() {}
virtual ~JSExecutor() {}
virtual void flush() {}
static std::string getSyntheticBundlePath(
uint32_t bundleId,
const std::string& bundlePath);

View File

@@ -83,9 +83,10 @@ public:
* Synchronously tears down the bridge and the main executor.
*/
void destroy();
private:
void runOnExecutorQueue(std::function<void(JSExecutor*)> task);
private:
// This is used to avoid a race condition where a proxyCallback gets queued
// after ~NativeToJsBridge(), on the same thread. In that case, the callback
// will try to run the task on m_callback which will have been destroyed

View File

@@ -103,10 +103,11 @@ class JSIExecutor : public JSExecutor {
invokee();
}
void flush() override;
private:
class NativeModuleProxy;
void flush();
void bindBridge();
void callNativeModules(const jsi::Value &queue, bool isEndOfBatch);
jsi::Value nativeCallSyncHook(const jsi::Value *args, size_t count);

View File

@@ -7,20 +7,19 @@
#include "JSCallInvoker.h"
#include <cxxreact/MessageQueueThread.h>
#include <cxxreact/Instance.h>
namespace facebook {
namespace react {
JSCallInvoker::JSCallInvoker(std::shared_ptr<MessageQueueThread> jsThread)
: jsThread_(jsThread) {}
JSCallInvoker::JSCallInvoker(std::shared_ptr<Instance> reactInstance)
: reactInstance_(reactInstance) {}
void JSCallInvoker::invokeAsync(std::function<void()>&& func) {
jsThread_->runOnQueue(std::move(func));
}
void JSCallInvoker::invokeSync(std::function<void()>&& func) {
jsThread_->runOnQueueSync(std::move(func));
if (reactInstance_ == nullptr) {
return;
}
reactInstance_->invokeAsync(std::move(func));
}
} // namespace react

View File

@@ -13,25 +13,26 @@
namespace facebook {
namespace react {
class MessageQueueThread;
class Instance;
/**
* A generic native-to-JS call invoker. It guarantees that any calls from any
* thread are queued on the right JS thread.
*
* For now, this is a thin-wrapper around existing MessageQueueThread. Eventually,
* For now, this is a thin-wrapper around existing bridge (`Instance`). Eventually,
* it should be consolidated with Fabric implementation so there's only one
* API to call JS from native, whether synchronously or asynchronously.
* Also, this class should not depend on `Instance` in the future.
*/
class JSCallInvoker {
public:
JSCallInvoker(std::shared_ptr<MessageQueueThread> jsThread);
JSCallInvoker(std::shared_ptr<Instance> reactInstance);
void invokeAsync(std::function<void()>&& func);
void invokeSync(std::function<void()>&& func);
// TODO: add sync support
private:
std::shared_ptr<MessageQueueThread> jsThread_;
std::shared_ptr<Instance> reactInstance_;
};
} // namespace react

View File

@@ -24,6 +24,8 @@
namespace facebook {
namespace react {
class Instance;
/**
* ObjC++ specific TurboModule base class.
*/
@@ -89,5 +91,6 @@ private:
@interface RCTBridge ()
- (std::shared_ptr<facebook::react::MessageQueueThread>)jsMessageThread;
- (std::shared_ptr<facebook::react::Instance>)reactInstance;
@end

View File

@@ -51,7 +51,7 @@ static Class getFallbackClassFromName(const char *name) {
{
if (self = [super init]) {
_runtime = runtime;
_jsInvoker = std::make_shared<react::JSCallInvoker>(bridge.jsMessageThread);
_jsInvoker = std::make_shared<react::JSCallInvoker>(bridge.reactInstance);
_delegate = delegate;
_bridge = bridge;