Implement incremental module requires

Differential Revision: D3234844

fbshipit-source-id: 24615528ad6a049aad7c2dbb7ce55e8b034c79e7
This commit is contained in:
Marc Horowitz 2016-05-18 12:46:01 -07:00 committed by Facebook Github Bot 5
parent 7f1346bfdd
commit 779314a413
11 changed files with 605 additions and 534 deletions

View File

@ -1,277 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "Bridge.h"
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
using fbsystrace::FbSystraceAsyncFlow;
#endif
#include <folly/json.h>
#include <folly/Memory.h>
#include <folly/MoveWrapper.h>
#include "Platform.h"
#include "SystraceSection.h"
namespace facebook {
namespace react {
Bridge::Bridge(
JSExecutorFactory* jsExecutorFactory,
std::shared_ptr<MessageQueueThread> jsQueue,
std::unique_ptr<ExecutorTokenFactory> executorTokenFactory,
std::unique_ptr<BridgeCallback> callback) :
m_callback(std::move(callback)),
m_destroyed(std::make_shared<bool>(false)),
m_executorTokenFactory(std::move(executorTokenFactory)) {
std::unique_ptr<JSExecutor> mainExecutor = jsExecutorFactory->createJSExecutor(this, jsQueue);
// cached to avoid locked map lookup in the common case
m_mainExecutor = mainExecutor.get();
m_mainExecutorToken = folly::make_unique<ExecutorToken>(registerExecutor(
std::move(mainExecutor), jsQueue));
}
// This must be called on the same thread on which the constructor was called.
Bridge::~Bridge() {
CHECK(*m_destroyed) << "Bridge::destroy() must be called before deallocating the Bridge!";
}
void Bridge::loadApplicationScript(std::unique_ptr<const JSBigString> script,
std::string sourceURL) {
// TODO(t11144533): Add assert that we are on the correct thread
m_mainExecutor->loadApplicationScript(std::move(script), std::move(sourceURL));
}
void Bridge::loadApplicationUnbundle(
std::unique_ptr<JSModulesUnbundle> unbundle,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
runOnExecutorQueue(
*m_mainExecutorToken,
[unbundle=folly::makeMoveWrapper(std::move(unbundle)),
startupScript=folly::makeMoveWrapper(std::move(startupScript)),
startupScriptSourceURL=std::move(startupScriptSourceURL)]
(JSExecutor* executor) mutable {
executor->setJSModulesUnbundle(unbundle.move());
executor->loadApplicationScript(std::move(*startupScript),
std::move(startupScriptSourceURL));
});
}
void Bridge::callFunction(
ExecutorToken executorToken,
const std::string& moduleId,
const std::string& methodId,
const folly::dynamic& arguments,
const std::string& tracingName) {
#ifdef WITH_FBSYSTRACE
int systraceCookie = m_systraceCookie++;
FbSystraceAsyncFlow::begin(
TRACE_TAG_REACT_CXX_BRIDGE,
tracingName.c_str(),
systraceCookie);
#endif
runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName, systraceCookie] (JSExecutor* executor) {
#ifdef WITH_FBSYSTRACE
FbSystraceAsyncFlow::end(
TRACE_TAG_REACT_CXX_BRIDGE,
tracingName.c_str(),
systraceCookie);
SystraceSection s(tracingName.c_str());
#endif
// This is safe because we are running on the executor's thread: it won't
// destruct until after it's been unregistered (which we check above) and
// that will happen on this thread
executor->callFunction(moduleId, methodId, arguments);
});
}
void Bridge::invokeCallback(ExecutorToken executorToken, const double callbackId, const folly::dynamic& arguments) {
#ifdef WITH_FBSYSTRACE
int systraceCookie = m_systraceCookie++;
FbSystraceAsyncFlow::begin(
TRACE_TAG_REACT_CXX_BRIDGE,
"<callback>",
systraceCookie);
#endif
runOnExecutorQueue(executorToken, [callbackId, arguments, systraceCookie] (JSExecutor* executor) {
#ifdef WITH_FBSYSTRACE
FbSystraceAsyncFlow::end(
TRACE_TAG_REACT_CXX_BRIDGE,
"<callback>",
systraceCookie);
SystraceSection s("Bridge.invokeCallback");
#endif
executor->invokeCallback(callbackId, arguments);
});
}
void Bridge::setGlobalVariable(std::string propName,
std::unique_ptr<const JSBigString> jsonValue) {
runOnExecutorQueue(
*m_mainExecutorToken,
[propName=std::move(propName), jsonValue=folly::makeMoveWrapper(std::move(jsonValue))]
(JSExecutor* executor) mutable {
executor->setGlobalVariable(propName, jsonValue.move());
});
}
void* Bridge::getJavaScriptContext() {
// TODO(cjhopman): this seems unsafe unless we require that it is only called on the main js queue.
return m_mainExecutor->getJavaScriptContext();
}
bool Bridge::supportsProfiling() {
// Intentionally doesn't post to jsqueue. supportsProfiling() can be called from any thread.
return m_mainExecutor->supportsProfiling();
}
void Bridge::startProfiler(const std::string& title) {
runOnExecutorQueue(*m_mainExecutorToken, [=] (JSExecutor* executor) {
executor->startProfiler(title);
});
}
void Bridge::stopProfiler(const std::string& title, const std::string& filename) {
runOnExecutorQueue(*m_mainExecutorToken, [=] (JSExecutor* executor) {
executor->stopProfiler(title, filename);
});
}
void Bridge::handleMemoryPressureModerate() {
runOnExecutorQueue(*m_mainExecutorToken, [=] (JSExecutor* executor) {
executor->handleMemoryPressureModerate();
});
}
void Bridge::handleMemoryPressureCritical() {
runOnExecutorQueue(*m_mainExecutorToken, [=] (JSExecutor* executor) {
executor->handleMemoryPressureCritical();
});
}
void Bridge::callNativeModules(JSExecutor& executor, const std::string& callJSON, bool isEndOfBatch) {
// This is called by the executor and thus runs on the executor's own queue.
// This means that the executor has not yet been unregistered (and we are
// guaranteed to be able to get the token).
m_callback->onCallNativeModules(getTokenForExecutor(executor), callJSON, isEndOfBatch);
}
MethodCallResult Bridge::callSerializableNativeHook(unsigned int moduleId, unsigned int methodId, const std::string& argsJSON) {
return m_callback->callSerializableNativeHook(*m_mainExecutorToken, moduleId, methodId, folly::parseJson(argsJSON));
}
ExecutorToken Bridge::getMainExecutorToken() const {
return *m_mainExecutorToken.get();
}
ExecutorToken Bridge::registerExecutor(
std::unique_ptr<JSExecutor> executor,
std::shared_ptr<MessageQueueThread> messageQueueThread) {
auto token = m_executorTokenFactory->createExecutorToken();
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
CHECK(m_executorTokenMap.find(executor.get()) == m_executorTokenMap.end())
<< "Trying to register an already registered executor!";
m_executorTokenMap.emplace(executor.get(), token);
m_executorMap.emplace(
token,
folly::make_unique<ExecutorRegistration>(std::move(executor), std::move(messageQueueThread)));
return token;
}
std::unique_ptr<JSExecutor> Bridge::unregisterExecutor(ExecutorToken executorToken) {
std::unique_ptr<JSExecutor> executor;
{
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
auto it = m_executorMap.find(executorToken);
CHECK(it != m_executorMap.end())
<< "Trying to unregister an executor that was never registered!";
executor = std::move(it->second->executor_);
m_executorMap.erase(it);
m_executorTokenMap.erase(executor.get());
}
m_callback->onExecutorUnregistered(executorToken);
return executor;
}
MessageQueueThread* Bridge::getMessageQueueThread(const ExecutorToken& executorToken) {
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
auto it = m_executorMap.find(executorToken);
if (it == m_executorMap.end()) {
return nullptr;
}
return it->second->messageQueueThread_.get();
}
JSExecutor* Bridge::getExecutor(const ExecutorToken& executorToken) {
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
auto it = m_executorMap.find(executorToken);
if (it == m_executorMap.end()) {
return nullptr;
}
return it->second->executor_.get();
}
ExecutorToken Bridge::getTokenForExecutor(JSExecutor& executor) {
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
return m_executorTokenMap.at(&executor);
}
void Bridge::destroy() {
auto executorMessageQueueThread = getMessageQueueThread(*m_mainExecutorToken);
executorMessageQueueThread->runOnQueueSync([this, &executorMessageQueueThread] {
executorMessageQueueThread->quitSynchronous();
*m_destroyed = true;
m_mainExecutor = nullptr;
std::unique_ptr<JSExecutor> mainExecutor = unregisterExecutor(*m_mainExecutorToken);
mainExecutor->destroy();
});
}
void Bridge::runOnExecutorQueue(ExecutorToken executorToken, std::function<void(JSExecutor*)> task) {
if (*m_destroyed) {
return;
}
auto executorMessageQueueThread = getMessageQueueThread(executorToken);
if (executorMessageQueueThread == nullptr) {
LOG(WARNING) << "Dropping JS action for executor that has been unregistered...";
return;
}
std::shared_ptr<bool> isDestroyed = m_destroyed;
executorMessageQueueThread->runOnQueue([this, isDestroyed, executorToken, task=std::move(task)] {
if (*isDestroyed) {
return;
}
JSExecutor *executor = getExecutor(executorToken);
if (executor == nullptr) {
LOG(WARNING) << "Dropping JS call for executor that has been unregistered...";
return;
}
// The executor is guaranteed to be valid for the duration of the task because:
// 1. the executor is only destroyed after it is unregistered
// 2. the executor is unregistered on this queue
// 3. we just confirmed that the executor hasn't been unregistered above
task(executor);
});
}
} }

View File

@ -7,26 +7,45 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <folly/dynamic.h>
#include "JSModulesUnbundle.h" #include "JSModulesUnbundle.h"
namespace folly {
struct dynamic;
}
namespace facebook { namespace facebook {
namespace react { namespace react {
class Bridge;
class JSExecutor; class JSExecutor;
class MessageQueueThread; class MessageQueueThread;
struct MethodCallResult {
folly::dynamic result;
bool isUndefined;
};
// This interface describes the delegate interface required by
// Executor implementations to call from JS into native code.
class ExecutorDelegate {
public:
virtual ~ExecutorDelegate() {}
virtual void registerExecutor(std::unique_ptr<JSExecutor> executor,
std::shared_ptr<MessageQueueThread> queue) = 0;
virtual std::unique_ptr<JSExecutor> unregisterExecutor(JSExecutor& executor) = 0;
virtual std::vector<std::string> moduleNames() = 0;
virtual folly::dynamic getModuleConfig(const std::string& name) = 0;
virtual void callNativeModules(
JSExecutor& executor, std::string callJSON, bool isEndOfBatch) = 0;
virtual MethodCallResult callSerializableNativeHook(
JSExecutor& executor, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args) = 0;
};
class JSExecutorFactory { class JSExecutorFactory {
public: public:
virtual std::unique_ptr<JSExecutor> createJSExecutor( virtual std::unique_ptr<JSExecutor> createJSExecutor(
Bridge *bridge, std::shared_ptr<MessageQueueThread> jsQueue) = 0; std::shared_ptr<ExecutorDelegate> delegate,
virtual ~JSExecutorFactory() {}; std::shared_ptr<MessageQueueThread> jsQueue) = 0;
virtual ~JSExecutorFactory() {}
}; };
// JSExecutor functions sometimes take large strings, on the order of // JSExecutor functions sometimes take large strings, on the order of
@ -143,18 +162,18 @@ public:
std::unique_ptr<const JSBigString> jsonValue) = 0; std::unique_ptr<const JSBigString> jsonValue) = 0;
virtual void* getJavaScriptContext() { virtual void* getJavaScriptContext() {
return nullptr; return nullptr;
}; }
virtual bool supportsProfiling() { virtual bool supportsProfiling() {
return false; return false;
}; }
virtual void startProfiler(const std::string &titleString) {}; virtual void startProfiler(const std::string &titleString) {}
virtual void stopProfiler(const std::string &titleString, const std::string &filename) {}; virtual void stopProfiler(const std::string &titleString, const std::string &filename) {}
virtual void handleMemoryPressureModerate() {}; virtual void handleMemoryPressureModerate() {}
virtual void handleMemoryPressureCritical() { virtual void handleMemoryPressureCritical() {
handleMemoryPressureModerate(); handleMemoryPressureModerate();
}; }
virtual void destroy() {}; virtual void destroy() {}
virtual ~JSExecutor() {}; virtual ~JSExecutor() {}
}; };
} } } }

View File

@ -8,6 +8,7 @@
#include <folly/json.h> #include <folly/json.h>
#include <folly/Memory.h> #include <folly/Memory.h>
#include <folly/MoveWrapper.h>
#include <glog/logging.h> #include <glog/logging.h>
@ -19,42 +20,9 @@
namespace facebook { namespace facebook {
namespace react { namespace react {
namespace {
struct ExecutorTokenFactoryImpl : ExecutorTokenFactory {
ExecutorTokenFactoryImpl(InstanceCallback* callback): callback_(callback) {}
virtual ExecutorToken createExecutorToken() const {
return callback_->createExecutorToken();
}
private:
InstanceCallback* callback_;
};
}
class Instance::BridgeCallbackImpl : public BridgeCallback {
public:
explicit BridgeCallbackImpl(Instance* instance) : instance_(instance) {}
virtual void onCallNativeModules(
ExecutorToken executorToken,
const std::string& calls,
bool isEndOfBatch) override {
instance_->callNativeModules(executorToken, calls, isEndOfBatch);
}
virtual void onExecutorUnregistered(ExecutorToken executorToken) override {
// TODO(cjhopman): implement this.
}
virtual MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int hookId, folly::dynamic&& params) override {
return instance_->callSerializableNativeHook(token, moduleId, hookId, std::move(params));
}
private:
Instance* instance_;
};
Instance::~Instance() { Instance::~Instance() {
if (nativeQueue_) { if (nativeToJsBridge_) {
nativeQueue_->quitSynchronous(); nativeToJsBridge_->destroy();
bridge_->destroy();
} }
} }
@ -65,32 +33,15 @@ void Instance::initializeBridge(
std::unique_ptr<MessageQueueThread> nativeQueue, std::unique_ptr<MessageQueueThread> nativeQueue,
std::shared_ptr<ModuleRegistry> moduleRegistry) { std::shared_ptr<ModuleRegistry> moduleRegistry) {
callback_ = std::move(callback); callback_ = std::move(callback);
nativeQueue_ = std::move(nativeQueue);
jsQueue_ = jsQueue;
moduleRegistry_ = moduleRegistry;
jsQueue_->runOnQueueSync([this, &jsef] { jsQueue->runOnQueueSync(
bridge_ = folly::make_unique<Bridge>( [this, &jsef, moduleRegistry, jsQueue,
jsef.get(), jsQueue_, folly::make_unique<ExecutorTokenFactoryImpl>(callback_.get()), folly::make_unique<BridgeCallbackImpl>(this)); nativeQueue=folly::makeMoveWrapper(std::move(nativeQueue))] () mutable {
}); nativeToJsBridge_ = folly::make_unique<NativeToJsBridge>(
SystraceSection s("setBatchedBridgeConfig"); jsef.get(), moduleRegistry, jsQueue, nativeQueue.move(), callback_);
});
CHECK(bridge_); CHECK(nativeToJsBridge_);
folly::dynamic nativeModuleDescriptions = folly::dynamic::array();
{
SystraceSection s("collectNativeModuleDescriptions");
nativeModuleDescriptions = moduleRegistry_->moduleDescriptions();
}
folly::dynamic config =
folly::dynamic::object
("remoteModuleConfig", std::move(nativeModuleDescriptions));
SystraceSection t("setGlobalVariable");
setGlobalVariable(
"__fbBatchedBridgeConfig",
folly::make_unique<JSBigStdString>(folly::toJson(config)));
} }
void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string, void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
@ -99,7 +50,7 @@ void Instance::loadScriptFromString(std::unique_ptr<const JSBigString> string,
SystraceSection s("reactbridge_xplat_loadScriptFromString", SystraceSection s("reactbridge_xplat_loadScriptFromString",
"sourceURL", sourceURL); "sourceURL", sourceURL);
// TODO mhorowitz: ReactMarker around loadApplicationScript // TODO mhorowitz: ReactMarker around loadApplicationScript
bridge_->loadApplicationScript(std::move(string), std::move(sourceURL)); nativeToJsBridge_->loadApplicationScript(std::move(string), std::move(sourceURL));
} }
void Instance::loadScriptFromFile(const std::string& filename, void Instance::loadScriptFromFile(const std::string& filename,
@ -129,71 +80,42 @@ void Instance::loadUnbundle(std::unique_ptr<JSModulesUnbundle> unbundle,
std::string startupScriptSourceURL) { std::string startupScriptSourceURL) {
callback_->incrementPendingJSCalls(); callback_->incrementPendingJSCalls();
SystraceSection s("reactbridge_xplat_setJSModulesUnbundle"); SystraceSection s("reactbridge_xplat_setJSModulesUnbundle");
bridge_->loadApplicationUnbundle(std::move(unbundle), std::move(startupScript), nativeToJsBridge_->loadApplicationUnbundle(std::move(unbundle), std::move(startupScript),
std::move(startupScriptSourceURL)); std::move(startupScriptSourceURL));
} }
bool Instance::supportsProfiling() { bool Instance::supportsProfiling() {
return bridge_->supportsProfiling(); return nativeToJsBridge_->supportsProfiling();
} }
void Instance::startProfiler(const std::string& title) { void Instance::startProfiler(const std::string& title) {
return bridge_->startProfiler(title); return nativeToJsBridge_->startProfiler(title);
} }
void Instance::stopProfiler(const std::string& title, const std::string& filename) { void Instance::stopProfiler(const std::string& title, const std::string& filename) {
return bridge_->stopProfiler(title, filename); return nativeToJsBridge_->stopProfiler(title, filename);
} }
void Instance::setGlobalVariable(std::string propName, void Instance::setGlobalVariable(std::string propName,
std::unique_ptr<const JSBigString> jsonValue) { std::unique_ptr<const JSBigString> jsonValue) {
bridge_->setGlobalVariable(std::move(propName), std::move(jsonValue)); nativeToJsBridge_->setGlobalVariable(std::move(propName), std::move(jsonValue));
} }
void Instance::callJSFunction(ExecutorToken token, const std::string& module, const std::string& method, void Instance::callJSFunction(ExecutorToken token, const std::string& module, const std::string& method,
folly::dynamic&& params, const std::string& tracingName) { folly::dynamic&& params, const std::string& tracingName) {
SystraceSection s(tracingName.c_str()); SystraceSection s(tracingName.c_str());
callback_->incrementPendingJSCalls(); callback_->incrementPendingJSCalls();
bridge_->callFunction(token, module, method, std::move(params), tracingName); nativeToJsBridge_->callFunction(token, module, method, std::move(params), tracingName);
} }
void Instance::callJSCallback(ExecutorToken token, uint64_t callbackId, folly::dynamic&& params) { void Instance::callJSCallback(ExecutorToken token, uint64_t callbackId, folly::dynamic&& params) {
SystraceSection s("<callback>"); SystraceSection s("<callback>");
callback_->incrementPendingJSCalls(); callback_->incrementPendingJSCalls();
bridge_->invokeCallback(token, (double) callbackId, std::move(params)); nativeToJsBridge_->invokeCallback(token, (double) callbackId, std::move(params));
} }
ExecutorToken Instance::getMainExecutorToken() { ExecutorToken Instance::getMainExecutorToken() {
return bridge_->getMainExecutorToken(); return nativeToJsBridge_->getMainExecutorToken();
}
void Instance::callNativeModules(ExecutorToken token, const std::string& calls, bool isEndOfBatch) {
// TODO mhorowitz: avoid copying calls here.
nativeQueue_->runOnQueue([this, token, calls, isEndOfBatch] {
try {
// An exception anywhere in here stops processing of the batch. This
// was the behavior of the Android bridge, and since exception handling
// terminates the whole bridge, there's not much point in continuing.
for (auto& call : react::parseMethodCalls(calls)) {
moduleRegistry_->callNativeMethod(
token, call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
if (isEndOfBatch) {
callback_->onBatchComplete();
callback_->decrementPendingJSCalls();
}
} catch (const std::exception& e) {
LOG(ERROR) << folly::exceptionStr(e).toStdString();
callback_->onNativeException(folly::exceptionStr(e).toStdString());
} catch (...) {
LOG(ERROR) << "Unknown exception";
callback_->onNativeException("Unknown exception");
}
});
}
MethodCallResult Instance::callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& params) {
return moduleRegistry_->callSerializableNativeHook(token, moduleId, methodId, std::move(params));
} }
} // namespace react } // namespace react

View File

@ -6,7 +6,7 @@
#include <folly/dynamic.h> #include <folly/dynamic.h>
#include "Bridge.h" #include "NativeToJsBridge.h"
#include "ModuleRegistry.h" #include "ModuleRegistry.h"
#include "NativeModule.h" #include "NativeModule.h"
@ -22,6 +22,7 @@ struct InstanceCallback {
virtual void decrementPendingJSCalls() = 0; virtual void decrementPendingJSCalls() = 0;
virtual void onNativeException(const std::string& what) = 0; virtual void onNativeException(const std::string& what) = 0;
virtual ExecutorToken createExecutorToken() = 0; virtual ExecutorToken createExecutorToken() = 0;
virtual void onExecutorStopped(ExecutorToken) = 0;
}; };
class Instance { class Instance {
@ -46,21 +47,15 @@ class Instance {
void callJSFunction(ExecutorToken token, const std::string& module, const std::string& method, void callJSFunction(ExecutorToken token, const std::string& module, const std::string& method,
folly::dynamic&& params, const std::string& tracingName); folly::dynamic&& params, const std::string& tracingName);
void callJSCallback(ExecutorToken token, uint64_t callbackId, folly::dynamic&& params); void callJSCallback(ExecutorToken token, uint64_t callbackId, folly::dynamic&& params);
MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args); MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId,
unsigned int methodId, folly::dynamic&& args);
ExecutorToken getMainExecutorToken(); ExecutorToken getMainExecutorToken();
private: private:
class BridgeCallbackImpl;
void callNativeModules(ExecutorToken token, const std::string& calls, bool isEndOfBatch); void callNativeModules(ExecutorToken token, const std::string& calls, bool isEndOfBatch);
std::unique_ptr<InstanceCallback> callback_; std::shared_ptr<InstanceCallback> callback_;
std::shared_ptr<ModuleRegistry> moduleRegistry_; std::unique_ptr<NativeToJsBridge> nativeToJsBridge_;
// TODO #10487027: clean up the ownership of this.
std::shared_ptr<MessageQueueThread> jsQueue_;
std::unique_ptr<MessageQueueThread> nativeQueue_;
std::unique_ptr<Bridge> bridge_;
}; };
} }

View File

@ -14,7 +14,6 @@
#include <folly/Conv.h> #include <folly/Conv.h>
#include <sys/time.h> #include <sys/time.h>
#include "Bridge.h"
#include "JSCHelpers.h" #include "JSCHelpers.h"
#include "Platform.h" #include "Platform.h"
#include "SystraceSection.h" #include "SystraceSection.h"
@ -107,29 +106,51 @@ static std::string executeJSCallWithJSC(
} }
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor( std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
Bridge *bridge, std::shared_ptr<MessageQueueThread> jsQueue) { std::shared_ptr<ExecutorDelegate> delegate, std::shared_ptr<MessageQueueThread> jsQueue) {
return std::unique_ptr<JSExecutor>( return std::unique_ptr<JSExecutor>(
new JSCExecutor(bridge, jsQueue, cacheDir_, m_jscConfig)); new JSCExecutor(delegate, jsQueue, m_cacheDir, m_jscConfig));
} }
JSCExecutor::JSCExecutor(Bridge *bridge, std::shared_ptr<MessageQueueThread> messageQueueThread, JSCExecutor::JSCExecutor(std::shared_ptr<ExecutorDelegate> delegate,
const std::string& cacheDir, const folly::dynamic& jscConfig) : std::shared_ptr<MessageQueueThread> messageQueueThread,
m_bridge(bridge), const std::string& cacheDir,
const folly::dynamic& jscConfig) :
m_delegate(delegate),
m_deviceCacheDir(cacheDir), m_deviceCacheDir(cacheDir),
m_messageQueueThread(messageQueueThread), m_messageQueueThread(messageQueueThread),
m_jscConfig(jscConfig) { m_jscConfig(jscConfig) {
initOnJSVMThread(); initOnJSVMThread();
SystraceSection s("setBatchedBridgeConfig");
folly::dynamic nativeModuleConfig = folly::dynamic::array();
{
SystraceSection s("collectNativeModuleNames");
std::vector<std::string> names = delegate->moduleNames();
for (auto& name : delegate->moduleNames()) {
nativeModuleConfig.push_back(folly::dynamic::array(std::move(name)));
}
}
folly::dynamic config =
folly::dynamic::object("remoteModuleConfig", std::move(nativeModuleConfig));
SystraceSection t("setGlobalVariable");
setGlobalVariable(
"__fbBatchedBridgeConfig",
folly::make_unique<JSBigStdString>(folly::toJson(config)));
} }
JSCExecutor::JSCExecutor( JSCExecutor::JSCExecutor(
Bridge *bridge, std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> messageQueueThread, std::shared_ptr<MessageQueueThread> messageQueueThread,
int workerId, int workerId,
JSCExecutor *owner, JSCExecutor *owner,
std::string scriptURL, std::string scriptURL,
std::unordered_map<std::string, std::string> globalObjAsJSON, std::unordered_map<std::string, std::string> globalObjAsJSON,
const folly::dynamic& jscConfig) : const folly::dynamic& jscConfig) :
m_bridge(bridge), m_delegate(delegate),
m_workerId(workerId), m_workerId(workerId),
m_owner(owner), m_owner(owner),
m_deviceCacheDir(owner->m_deviceCacheDir), m_deviceCacheDir(owner->m_deviceCacheDir),
@ -198,6 +219,7 @@ void JSCExecutor::initOnJSVMThread() {
// Add a pointer to ourselves so we can retrieve it later in our hooks // Add a pointer to ourselves so we can retrieve it later in our hooks
JSObjectSetPrivate(JSContextGetGlobalObject(m_context), this); JSObjectSetPrivate(JSContextGetGlobalObject(m_context), this);
installNativeHook<&JSCExecutor::nativeRequireModuleConfig>("nativeRequireModuleConfig");
installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate"); installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate");
installNativeHook<&JSCExecutor::nativeStartWorker>("nativeStartWorker"); installNativeHook<&JSCExecutor::nativeStartWorker>("nativeStartWorker");
installNativeHook<&JSCExecutor::nativePostMessageToWorker>("nativePostMessageToWorker"); installNativeHook<&JSCExecutor::nativePostMessageToWorker>("nativePostMessageToWorker");
@ -280,7 +302,7 @@ void JSCExecutor::setJSModulesUnbundle(std::unique_ptr<JSModulesUnbundle> unbund
void JSCExecutor::flush() { void JSCExecutor::flush() {
// TODO: Make this a first class function instead of evaling. #9317773 // TODO: Make this a first class function instead of evaling. #9317773
std::string calls = executeJSCallWithJSC(m_context, "flushedQueue", std::vector<folly::dynamic>()); std::string calls = executeJSCallWithJSC(m_context, "flushedQueue", std::vector<folly::dynamic>());
m_bridge->callNativeModules(*this, calls, true); m_delegate->callNativeModules(*this, std::move(calls), true);
} }
void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) { void JSCExecutor::callFunction(const std::string& moduleId, const std::string& methodId, const folly::dynamic& arguments) {
@ -291,7 +313,7 @@ void JSCExecutor::callFunction(const std::string& moduleId, const std::string& m
std::move(arguments), std::move(arguments),
}; };
std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call)); std::string calls = executeJSCallWithJSC(m_context, "callFunctionReturnFlushedQueue", std::move(call));
m_bridge->callNativeModules(*this, calls, true); m_delegate->callNativeModules(*this, std::move(calls), true);
} }
void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) { void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& arguments) {
@ -301,7 +323,7 @@ void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic&
std::move(arguments) std::move(arguments)
}; };
std::string calls = executeJSCallWithJSC(m_context, "invokeCallbackAndReturnFlushedQueue", std::move(call)); std::string calls = executeJSCallWithJSC(m_context, "invokeCallbackAndReturnFlushedQueue", std::move(call));
m_bridge->callNativeModules(*this, calls, true); m_delegate->callNativeModules(*this, std::move(calls), true);
} }
void JSCExecutor::setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue) { void JSCExecutor::setGlobalVariable(std::string propName, std::unique_ptr<const JSBigString> jsonValue) {
@ -362,7 +384,7 @@ void JSCExecutor::handleMemoryPressureCritical() {
} }
void JSCExecutor::flushQueueImmediate(std::string queueJSON) { void JSCExecutor::flushQueueImmediate(std::string queueJSON) {
m_bridge->callNativeModules(*this, queueJSON, false); m_delegate->callNativeModules(*this, std::move(queueJSON), false);
} }
void JSCExecutor::loadModule(uint32_t moduleId) { void JSCExecutor::loadModule(uint32_t moduleId) {
@ -388,7 +410,7 @@ int JSCExecutor::addWebWorker(
WebWorkerUtil::createWebWorkerThread(workerId, m_messageQueueThread.get()); WebWorkerUtil::createWebWorkerThread(workerId, m_messageQueueThread.get());
std::unique_ptr<JSCExecutor> worker; std::unique_ptr<JSCExecutor> worker;
workerMQT->runOnQueueSync([this, &worker, &workerMQT, &scriptURL, &globalObj, workerId, &workerJscConfig] () { workerMQT->runOnQueueSync([this, &worker, &workerMQT, &scriptURL, &globalObj, workerId, &workerJscConfig] () {
worker.reset(new JSCExecutor(m_bridge, workerMQT, workerId, this, scriptURL, worker.reset(new JSCExecutor(m_delegate, workerMQT, workerId, this, std::move(scriptURL),
globalObj.toJSONMap(), workerJscConfig)); globalObj.toJSONMap(), workerJscConfig));
}); });
@ -397,14 +419,14 @@ int JSCExecutor::addWebWorker(
JSCExecutor *workerPtr = worker.get(); JSCExecutor *workerPtr = worker.get();
std::shared_ptr<MessageQueueThread> sharedMessageQueueThread = worker->m_messageQueueThread; std::shared_ptr<MessageQueueThread> sharedMessageQueueThread = worker->m_messageQueueThread;
ExecutorToken token = m_bridge->registerExecutor( m_delegate->registerExecutor(
std::move(worker), std::move(worker),
std::move(sharedMessageQueueThread)); std::move(sharedMessageQueueThread));
m_ownedWorkers.emplace( m_ownedWorkers.emplace(
std::piecewise_construct, std::piecewise_construct,
std::forward_as_tuple(workerId), std::forward_as_tuple(workerId),
std::forward_as_tuple(workerPtr, token, std::move(workerObj))); std::forward_as_tuple(workerPtr, std::move(workerObj)));
return workerId; return workerId;
} }
@ -464,12 +486,11 @@ void JSCExecutor::receiveMessageFromOwner(const std::string& msgString) {
void JSCExecutor::terminateOwnedWebWorker(int workerId) { void JSCExecutor::terminateOwnedWebWorker(int workerId) {
auto& workerRegistration = m_ownedWorkers.at(workerId); auto& workerRegistration = m_ownedWorkers.at(workerId);
std::shared_ptr<MessageQueueThread> workerMQT = workerRegistration.executor->m_messageQueueThread; std::shared_ptr<MessageQueueThread> workerMQT = workerRegistration.executor->m_messageQueueThread;
ExecutorToken workerExecutorToken = workerRegistration.executorToken;
m_ownedWorkers.erase(workerId); m_ownedWorkers.erase(workerId);
workerMQT->runOnQueueSync([this, workerExecutorToken, &workerMQT] { workerMQT->runOnQueueSync([this, &workerMQT] {
workerMQT->quitSynchronous(); workerMQT->quitSynchronous();
std::unique_ptr<JSExecutor> worker = m_bridge->unregisterExecutor(workerExecutorToken); std::unique_ptr<JSExecutor> worker = m_delegate->unregisterExecutor(*this);
worker->destroy(); worker->destroy();
worker.reset(); worker.reset();
}); });
@ -521,6 +542,18 @@ JSValueRef JSCExecutor::nativeRequire(
return JSValueMakeUndefined(m_context); return JSValueMakeUndefined(m_context);
} }
JSValueRef JSCExecutor::nativeRequireModuleConfig(
size_t argumentCount,
const JSValueRef arguments[]) {
if (argumentCount != 1) {
throw std::invalid_argument("Got wrong number of args");
}
std::string moduleName = Value(m_context, arguments[0]).toString().str();
folly::dynamic config = m_delegate->getModuleConfig(moduleName);
return JSValueMakeString(m_context, String(folly::toJson(config).c_str()));
}
JSValueRef JSCExecutor::nativeFlushQueueImmediate( JSValueRef JSCExecutor::nativeFlushQueueImmediate(
size_t argumentCount, size_t argumentCount,
const JSValueRef arguments[]) { const JSValueRef arguments[]) {
@ -529,7 +562,7 @@ JSValueRef JSCExecutor::nativeFlushQueueImmediate(
} }
std::string resStr = Value(m_context, arguments[0]).toJSONString(); std::string resStr = Value(m_context, arguments[0]).toJSONString();
flushQueueImmediate(resStr); flushQueueImmediate(std::move(resStr));
return JSValueMakeUndefined(m_context); return JSValueMakeUndefined(m_context);
} }
@ -595,7 +628,8 @@ JSValueRef JSCExecutor::nativeCallSyncHook(
unsigned int methodId = Value(m_context, arguments[1]).asUnsignedInteger(); unsigned int methodId = Value(m_context, arguments[1]).asUnsignedInteger();
std::string argsJson = Value(m_context, arguments[2]).toJSONString(); std::string argsJson = Value(m_context, arguments[2]).toJSONString();
MethodCallResult result = m_bridge->callSerializableNativeHook( MethodCallResult result = m_delegate->callSerializableNativeHook(
*this,
moduleId, moduleId,
methodId, methodId,
argsJson); argsJson);

View File

@ -23,25 +23,24 @@ class MessageQueueThread;
class JSCExecutorFactory : public JSExecutorFactory { class JSCExecutorFactory : public JSExecutorFactory {
public: public:
JSCExecutorFactory(const std::string& cacheDir, const folly::dynamic& jscConfig) : JSCExecutorFactory(const std::string& cacheDir, const folly::dynamic& jscConfig) :
cacheDir_(cacheDir), m_cacheDir(cacheDir),
m_jscConfig(jscConfig) {} m_jscConfig(jscConfig) {}
virtual std::unique_ptr<JSExecutor> createJSExecutor( virtual std::unique_ptr<JSExecutor> createJSExecutor(
Bridge *bridge, std::shared_ptr<MessageQueueThread> jsQueue) override; std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> jsQueue) override;
private: private:
std::string cacheDir_; std::string m_cacheDir;
folly::dynamic m_jscConfig; folly::dynamic m_jscConfig;
}; };
class JSCExecutor; class JSCExecutor;
class WorkerRegistration : public noncopyable { class WorkerRegistration : public noncopyable {
public: public:
explicit WorkerRegistration(JSCExecutor* executor_, ExecutorToken executorToken_, Object jsObj_) : explicit WorkerRegistration(JSCExecutor* executor_, Object jsObj_) :
executor(executor_), executor(executor_),
executorToken(executorToken_),
jsObj(std::move(jsObj_)) {} jsObj(std::move(jsObj_)) {}
JSCExecutor *executor; JSCExecutor *executor;
ExecutorToken executorToken;
Object jsObj; Object jsObj;
}; };
@ -50,8 +49,10 @@ public:
/** /**
* Must be invoked from thread this Executor will run on. * Must be invoked from thread this Executor will run on.
*/ */
explicit JSCExecutor(Bridge *bridge, std::shared_ptr<MessageQueueThread> messageQueueThread, explicit JSCExecutor(std::shared_ptr<ExecutorDelegate> delegate,
const std::string& cacheDir, const folly::dynamic& jscConfig); std::shared_ptr<MessageQueueThread> messageQueueThread,
const std::string& cacheDir,
const folly::dynamic& jscConfig);
~JSCExecutor() override; ~JSCExecutor() override;
virtual void loadApplicationScript( virtual void loadApplicationScript(
@ -79,7 +80,7 @@ public:
private: private:
JSGlobalContextRef m_context; JSGlobalContextRef m_context;
Bridge *m_bridge; std::shared_ptr<ExecutorDelegate> m_delegate;
int m_workerId = 0; // if this is a worker executor, this is non-zero int m_workerId = 0; // if this is a worker executor, this is non-zero
JSCExecutor *m_owner = nullptr; // if this is a worker executor, this is non-null JSCExecutor *m_owner = nullptr; // if this is a worker executor, this is non-null
std::shared_ptr<bool> m_isDestroyed = std::shared_ptr<bool>(new bool(false)); std::shared_ptr<bool> m_isDestroyed = std::shared_ptr<bool>(new bool(false));
@ -93,7 +94,7 @@ private:
* WebWorker constructor. Must be invoked from thread this Executor will run on. * WebWorker constructor. Must be invoked from thread this Executor will run on.
*/ */
JSCExecutor( JSCExecutor(
Bridge *bridge, std::shared_ptr<ExecutorDelegate> delegate,
std::shared_ptr<MessageQueueThread> messageQueueThread, std::shared_ptr<MessageQueueThread> messageQueueThread,
int workerId, int workerId,
JSCExecutor *owner, JSCExecutor *owner,
@ -118,6 +119,9 @@ private:
template< JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])> template< JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])>
void installNativeHook(const char* name); void installNativeHook(const char* name);
JSValueRef nativeRequireModuleConfig(
size_t argumentCount,
const JSValueRef arguments[]);
JSValueRef nativeStartWorker( JSValueRef nativeStartWorker(
size_t argumentCount, size_t argumentCount,
const JSValueRef arguments[]); const JSValueRef arguments[]);

View File

@ -8,45 +8,86 @@
namespace facebook { namespace facebook {
namespace react { namespace react {
namespace {
std::string normalizeName(std::string name) {
// TODO mhorowitz #10487027: This is super ugly. We should just
// change iOS to emit normalized names, drop the "RK..." from
// names hardcoded in Android, and then delete this and the
// similar hacks in js.
if (name.compare(0, 3, "RCT") == 0) {
return name.substr(3);
} else if (name.compare(0, 2, "RK") == 0) {
return name.substr(2);
}
return name;
}
}
ModuleRegistry::ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules) ModuleRegistry::ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules)
: modules_(std::move(modules)) {} : modules_(std::move(modules)) {}
folly::dynamic ModuleRegistry::moduleDescriptions() { std::vector<std::string> ModuleRegistry::moduleNames() {
folly::dynamic modDescs = folly::dynamic::object; std::vector<std::string> names;
for (size_t i = 0; i < modules_.size(); i++) {
for (size_t moduleId = 0; moduleId < modules_.size(); ++moduleId) { std::string name = normalizeName(modules_[i]->getName());
const auto& module = modules_[moduleId]; modulesByName_[name] = i;
names.push_back(std::move(name));
folly::dynamic methodDescs = folly::dynamic::object; }
std::vector<MethodDescriptor> methods; return names;
{ }
SystraceSection s("getMethods",
"module", module->getName()); folly::dynamic ModuleRegistry::getConfig(const std::string& name) {
methods = module->getMethods(); auto it = modulesByName_.find(name);
} if (it == modulesByName_.end()) {
for (size_t methodId = 0; methodId < methods.size(); ++methodId) { return nullptr;
methodDescs.insert(std::move(methods[methodId].name), }
folly::dynamic::object CHECK(it->second < modules_.size());
("methodID", methodId)
("type", std::move(methods[methodId].type))); NativeModule* module = modules_[it->second].get();
}
// string name, [object constants,] array methodNames (methodId is index), [array asyncMethodIds]
folly::dynamic constants = folly::dynamic::array(); folly::dynamic config = folly::dynamic::array(name);
{
SystraceSection s("getConstants", {
"module", module->getName()); SystraceSection s("getConfig constants",
constants = module->getConstants(); "module", name);
} folly::dynamic constants = module->getConstants();
if (constants.isObject() && constants.size() > 0) {
modDescs.insert(module->getName(), config.push_back(std::move(constants));
folly::dynamic::object }
("supportsWebWorkers", module->supportsWebWorkers()) }
("moduleID", moduleId)
("methods", std::move(methodDescs)) {
("constants", std::move(constants))); SystraceSection s("getConfig methods",
"module", name);
std::vector<MethodDescriptor> methods = module->getMethods();
folly::dynamic methodNames = folly::dynamic::array;
folly::dynamic asyncMethodIds = folly::dynamic::array;
for (auto& descriptor : methods) {
methodNames.push_back(std::move(descriptor.name));
if (descriptor.type == "remoteAsync") {
asyncMethodIds.push_back(methodNames.size() - 1);
}
}
if (!methodNames.empty()) {
config.push_back(std::move(methodNames));
if (!asyncMethodIds.empty()) {
config.push_back(std::move(asyncMethodIds));
}
}
}
if (config.size() == 1) {
// no constants or methods
return nullptr;
} else {
return config;
} }
return modDescs;
} }
void ModuleRegistry::callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId, void ModuleRegistry::callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId,
@ -75,7 +116,7 @@ void ModuleRegistry::callNativeMethod(ExecutorToken token, unsigned int moduleId
// fall through; // fall through;
} }
std::string moduleName = modules_[moduleId]->getName(); std::string moduleName = normalizeName(modules_[moduleId]->getName());
auto descs = modules_[moduleId]->getMethods(); auto descs = modules_[moduleId]->getMethods();
std::string methodName; std::string methodName;
if (methodId < descs.size()) { if (methodId < descs.size()) {

View File

@ -25,13 +25,18 @@ class ModuleRegistry {
// notifyCatalystInstanceDestroy: use RAII instead // notifyCatalystInstanceDestroy: use RAII instead
ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules); ModuleRegistry(std::vector<std::unique_ptr<NativeModule>> modules);
folly::dynamic moduleDescriptions(); std::vector<std::string> moduleNames();
folly::dynamic getConfig(const std::string& name);
void callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId, void callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId,
folly::dynamic&& params, int callId); folly::dynamic&& params, int callId);
MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args); MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args);
private: private:
// This is always populated
std::vector<std::unique_ptr<NativeModule>> modules_; std::vector<std::unique_ptr<NativeModule>> modules_;
// This is only populated if moduleNames() is called. Values are indices into modules_.
std::unordered_map<std::string, size_t> modulesByName_;
}; };
} }

View File

@ -12,11 +12,6 @@
namespace facebook { namespace facebook {
namespace react { namespace react {
struct MethodCallResult {
folly::dynamic result;
bool isUndefined;
};
struct MethodDescriptor { struct MethodDescriptor {
std::string name; std::string name;
// type is one of js MessageQueue.MethodTypes // type is one of js MessageQueue.MethodTypes

View File

@ -0,0 +1,347 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#include "NativeToJsBridge.h"
#ifdef WITH_FBSYSTRACE
#include <fbsystrace.h>
using fbsystrace::FbSystraceAsyncFlow;
#endif
#include <folly/json.h>
#include <folly/Memory.h>
#include <folly/MoveWrapper.h>
#include "Instance.h"
#include "ModuleRegistry.h"
#include "Platform.h"
#include "SystraceSection.h"
namespace facebook {
namespace react {
// This class manages calls from JS to native code.
class JsToNativeBridge : public react::ExecutorDelegate {
public:
JsToNativeBridge(NativeToJsBridge* nativeToJs,
std::shared_ptr<ModuleRegistry> registry,
std::unique_ptr<MessageQueueThread> nativeQueue,
std::shared_ptr<InstanceCallback> callback)
: m_nativeToJs(nativeToJs)
, m_registry(registry)
, m_nativeQueue(std::move(nativeQueue))
, m_callback(callback) {}
void registerExecutor(std::unique_ptr<JSExecutor> executor,
std::shared_ptr<MessageQueueThread> queue) override {
m_nativeToJs->registerExecutor(m_callback->createExecutorToken(), std::move(executor), queue);
}
std::unique_ptr<JSExecutor> unregisterExecutor(JSExecutor& executor) override {
m_callback->onExecutorStopped(m_nativeToJs->getTokenForExecutor(executor));
return m_nativeToJs->unregisterExecutor(executor);
}
std::vector<std::string> moduleNames() override {
// If this turns out to be too expensive to run on the js thread,
// we can compute it in the ctor, and just return std::move() it
// here.
return m_registry->moduleNames();
}
folly::dynamic getModuleConfig(const std::string& name) override {
return m_registry->getConfig(name);
}
void callNativeModules(
JSExecutor& executor, std::string callJSON, bool isEndOfBatch) override {
ExecutorToken token = m_nativeToJs->getTokenForExecutor(executor);
m_nativeQueue->runOnQueue([this, token, callJSON=std::move(callJSON), isEndOfBatch] {
// An exception anywhere in here stops processing of the batch. This
// was the behavior of the Android bridge, and since exception handling
// terminates the whole bridge, there's not much point in continuing.
for (auto& call : react::parseMethodCalls(callJSON)) {
m_registry->callNativeMethod(
token, call.moduleId, call.methodId, std::move(call.arguments), call.callId);
}
if (isEndOfBatch) {
m_callback->onBatchComplete();
m_callback->decrementPendingJSCalls();
}
});
}
MethodCallResult callSerializableNativeHook(
JSExecutor& executor, unsigned int moduleId, unsigned int methodId,
folly::dynamic&& args) override {
ExecutorToken token = m_nativeToJs->getTokenForExecutor(executor);
return m_registry->callSerializableNativeHook(token, moduleId, methodId, std::move(args));
}
void quitQueueSynchronous() {
m_nativeQueue->quitSynchronous();
}
private:
// These methods are always invoked from an Executor. The NativeToJsBridge
// keeps a reference to the root executor, and when destroy() is
// called, the Executors are all destroyed synchronously on their
// bridges. So, the bridge pointer will will always point to a
// valid object during a call to a delegate method from an exectuto.
NativeToJsBridge* m_nativeToJs;
std::shared_ptr<ModuleRegistry> m_registry;
std::unique_ptr<MessageQueueThread> m_nativeQueue;
std::shared_ptr<InstanceCallback> m_callback;
};
NativeToJsBridge::NativeToJsBridge(
JSExecutorFactory* jsExecutorFactory,
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<MessageQueueThread> jsQueue,
std::unique_ptr<MessageQueueThread> nativeQueue,
std::shared_ptr<InstanceCallback> callback)
: m_destroyed(std::make_shared<bool>(false))
, m_mainExecutorToken(callback->createExecutorToken())
, m_delegate(
std::make_shared<JsToNativeBridge>(
this, registry, std::move(nativeQueue), callback)) {
std::unique_ptr<JSExecutor> mainExecutor =
jsExecutorFactory->createJSExecutor(m_delegate, jsQueue);
// cached to avoid locked map lookup in the common case
m_mainExecutor = mainExecutor.get();
registerExecutor(m_mainExecutorToken, std::move(mainExecutor), jsQueue);
}
// This must be called on the same thread on which the constructor was called.
NativeToJsBridge::~NativeToJsBridge() {
CHECK(*m_destroyed) <<
"NativeToJsBridge::destroy() must be called before deallocating the NativeToJsBridge!";
}
void NativeToJsBridge::loadApplicationScript(std::unique_ptr<const JSBigString> script,
std::string sourceURL) {
// TODO(t11144533): Add assert that we are on the correct thread
m_mainExecutor->loadApplicationScript(std::move(script), std::move(sourceURL));
}
void NativeToJsBridge::loadApplicationUnbundle(
std::unique_ptr<JSModulesUnbundle> unbundle,
std::unique_ptr<const JSBigString> startupScript,
std::string startupScriptSourceURL) {
runOnExecutorQueue(
m_mainExecutorToken,
[unbundle=folly::makeMoveWrapper(std::move(unbundle)),
startupScript=folly::makeMoveWrapper(std::move(startupScript)),
startupScriptSourceURL=std::move(startupScriptSourceURL)]
(JSExecutor* executor) mutable {
executor->setJSModulesUnbundle(unbundle.move());
executor->loadApplicationScript(std::move(*startupScript),
std::move(startupScriptSourceURL));
});
}
void NativeToJsBridge::callFunction(
ExecutorToken executorToken,
const std::string& moduleId,
const std::string& methodId,
const folly::dynamic& arguments,
const std::string& tracingName) {
#ifdef WITH_FBSYSTRACE
int systraceCookie = m_systraceCookie++;
FbSystraceAsyncFlow::begin(
TRACE_TAG_REACT_CXX_BRIDGE,
tracingName.c_str(),
systraceCookie);
#endif
runOnExecutorQueue(executorToken, [moduleId, methodId, arguments, tracingName, systraceCookie] (JSExecutor* executor) {
#ifdef WITH_FBSYSTRACE
FbSystraceAsyncFlow::end(
TRACE_TAG_REACT_CXX_BRIDGE,
tracingName.c_str(),
systraceCookie);
SystraceSection s(tracingName.c_str());
#endif
// This is safe because we are running on the executor's thread: it won't
// destruct until after it's been unregistered (which we check above) and
// that will happen on this thread
executor->callFunction(moduleId, methodId, arguments);
});
}
void NativeToJsBridge::invokeCallback(ExecutorToken executorToken, const double callbackId,
const folly::dynamic& arguments) {
#ifdef WITH_FBSYSTRACE
int systraceCookie = m_systraceCookie++;
FbSystraceAsyncFlow::begin(
TRACE_TAG_REACT_CXX_BRIDGE,
"<callback>",
systraceCookie);
#endif
runOnExecutorQueue(executorToken, [callbackId, arguments, systraceCookie] (JSExecutor* executor) {
#ifdef WITH_FBSYSTRACE
FbSystraceAsyncFlow::end(
TRACE_TAG_REACT_CXX_BRIDGE,
"<callback>",
systraceCookie);
SystraceSection s("NativeToJsBridge.invokeCallback");
#endif
executor->invokeCallback(callbackId, arguments);
});
}
void NativeToJsBridge::setGlobalVariable(std::string propName,
std::unique_ptr<const JSBigString> jsonValue) {
runOnExecutorQueue(
m_mainExecutorToken,
[propName=std::move(propName), jsonValue=folly::makeMoveWrapper(std::move(jsonValue))]
(JSExecutor* executor) mutable {
executor->setGlobalVariable(propName, jsonValue.move());
});
}
void* NativeToJsBridge::getJavaScriptContext() {
// TODO(cjhopman): this seems unsafe unless we require that it is only called on the main js queue.
return m_mainExecutor->getJavaScriptContext();
}
bool NativeToJsBridge::supportsProfiling() {
// Intentionally doesn't post to jsqueue. supportsProfiling() can be called from any thread.
return m_mainExecutor->supportsProfiling();
}
void NativeToJsBridge::startProfiler(const std::string& title) {
runOnExecutorQueue(m_mainExecutorToken, [=] (JSExecutor* executor) {
executor->startProfiler(title);
});
}
void NativeToJsBridge::stopProfiler(const std::string& title, const std::string& filename) {
runOnExecutorQueue(m_mainExecutorToken, [=] (JSExecutor* executor) {
executor->stopProfiler(title, filename);
});
}
void NativeToJsBridge::handleMemoryPressureModerate() {
runOnExecutorQueue(m_mainExecutorToken, [=] (JSExecutor* executor) {
executor->handleMemoryPressureModerate();
});
}
void NativeToJsBridge::handleMemoryPressureCritical() {
runOnExecutorQueue(m_mainExecutorToken, [=] (JSExecutor* executor) {
executor->handleMemoryPressureCritical();
});
}
ExecutorToken NativeToJsBridge::getMainExecutorToken() const {
return m_mainExecutorToken;
}
ExecutorToken NativeToJsBridge::registerExecutor(
ExecutorToken token,
std::unique_ptr<JSExecutor> executor,
std::shared_ptr<MessageQueueThread> messageQueueThread) {
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
CHECK(m_executorTokenMap.find(executor.get()) == m_executorTokenMap.end())
<< "Trying to register an already registered executor!";
m_executorTokenMap.emplace(executor.get(), token);
m_executorMap.emplace(
token,
ExecutorRegistration(std::move(executor), messageQueueThread));
return token;
}
std::unique_ptr<JSExecutor> NativeToJsBridge::unregisterExecutor(JSExecutor& executor) {
std::unique_ptr<JSExecutor> ret;
{
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
auto it = m_executorTokenMap.find(&executor);
CHECK(it != m_executorTokenMap.end())
<< "Trying to unregister an executor that was never registered!";
auto it2 = m_executorMap.find(it->second);
ret = std::move(it2->second.executor_);
m_executorTokenMap.erase(it);
m_executorMap.erase(it2);
}
return ret;
}
MessageQueueThread* NativeToJsBridge::getMessageQueueThread(const ExecutorToken& executorToken) {
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
auto it = m_executorMap.find(executorToken);
if (it == m_executorMap.end()) {
return nullptr;
}
return it->second.messageQueueThread_.get();
}
JSExecutor* NativeToJsBridge::getExecutor(const ExecutorToken& executorToken) {
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
auto it = m_executorMap.find(executorToken);
if (it == m_executorMap.end()) {
return nullptr;
}
return it->second.executor_.get();
}
ExecutorToken NativeToJsBridge::getTokenForExecutor(JSExecutor& executor) {
std::lock_guard<std::mutex> registrationGuard(m_registrationMutex);
return m_executorTokenMap.at(&executor);
}
void NativeToJsBridge::destroy() {
m_delegate->quitQueueSynchronous();
auto executorMessageQueueThread = getMessageQueueThread(m_mainExecutorToken);
executorMessageQueueThread->runOnQueueSync([this, &executorMessageQueueThread] {
executorMessageQueueThread->quitSynchronous();
*m_destroyed = true;
std::unique_ptr<JSExecutor> mainExecutor = unregisterExecutor(*m_mainExecutor);
m_mainExecutor = nullptr;
mainExecutor->destroy();
});
}
void NativeToJsBridge::runOnExecutorQueue(ExecutorToken executorToken, std::function<void(JSExecutor*)> task) {
if (*m_destroyed) {
return;
}
auto executorMessageQueueThread = getMessageQueueThread(executorToken);
if (executorMessageQueueThread == nullptr) {
LOG(WARNING) << "Dropping JS action for executor that has been unregistered...";
return;
}
std::shared_ptr<bool> isDestroyed = m_destroyed;
executorMessageQueueThread->runOnQueue([this, isDestroyed, executorToken, task=std::move(task)] {
if (*isDestroyed) {
return;
}
JSExecutor *executor = getExecutor(executorToken);
if (executor == nullptr) {
LOG(WARNING) << "Dropping JS call for executor that has been unregistered...";
return;
}
// The executor is guaranteed to be valid for the duration of the task because:
// 1. the executor is only destroyed after it is unregistered
// 2. the executor is unregistered on this queue
// 3. we just confirmed that the executor hasn't been unregistered above
task(executor);
});
}
} }

View File

@ -11,7 +11,6 @@
#include "Executor.h" #include "Executor.h"
#include "ExecutorToken.h" #include "ExecutorToken.h"
#include "ExecutorTokenFactory.h"
#include "JSModulesUnbundle.h" #include "JSModulesUnbundle.h"
#include "MessageQueueThread.h" #include "MessageQueueThread.h"
#include "MethodCall.h" #include "MethodCall.h"
@ -27,21 +26,9 @@ struct dynamic;
namespace facebook { namespace facebook {
namespace react { namespace react {
class BridgeCallback { struct InstanceCallback;
public: class ModuleRegistry;
virtual ~BridgeCallback() {};
virtual void onCallNativeModules(
ExecutorToken executorToken,
const std::string& callJSON,
bool isEndOfBatch) = 0;
virtual void onExecutorUnregistered(ExecutorToken executorToken) = 0;
virtual MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args) = 0;
};
class Bridge;
class ExecutorRegistration { class ExecutorRegistration {
public: public:
ExecutorRegistration( ExecutorRegistration(
@ -54,17 +41,26 @@ public:
std::shared_ptr<MessageQueueThread> messageQueueThread_; std::shared_ptr<MessageQueueThread> messageQueueThread_;
}; };
class Bridge { class JsToNativeBridge;
// This class manages calls from native code to JS. It also manages
// executors and their threads. This part is used by both bridges for
// now, but further refactorings should separate the bridges more
// fully #11247981.
class NativeToJsBridge {
public: public:
friend class JsToNativeBridge;
/** /**
* This must be called on the main JS thread. * This must be called on the main JS thread.
*/ */
Bridge( NativeToJsBridge(
JSExecutorFactory* jsExecutorFactory, JSExecutorFactory* jsExecutorFactory,
std::shared_ptr<ModuleRegistry> registry,
std::shared_ptr<MessageQueueThread> jsQueue, std::shared_ptr<MessageQueueThread> jsQueue,
std::unique_ptr<ExecutorTokenFactory> executorTokenFactory, std::unique_ptr<MessageQueueThread> nativeQueue,
std::unique_ptr<BridgeCallback> callback); std::shared_ptr<InstanceCallback> callback);
virtual ~Bridge(); virtual ~NativeToJsBridge();
/** /**
* Executes a function with the module ID and method ID and any additional * Executes a function with the module ID and method ID and any additional
@ -108,58 +104,48 @@ public:
void handleMemoryPressureModerate(); void handleMemoryPressureModerate();
void handleMemoryPressureCritical(); void handleMemoryPressureCritical();
/**
* Invokes a set of native module calls on behalf of the given executor.
*
* TODO: get rid of isEndOfBatch
*/
void callNativeModules(JSExecutor& executor, const std::string& callJSON, bool isEndOfBatch);
MethodCallResult callSerializableNativeHook(unsigned int moduleId, unsigned int methodId, const std::string& argsJSON);
/** /**
* Returns the ExecutorToken corresponding to the main JSExecutor. * Returns the ExecutorToken corresponding to the main JSExecutor.
*/ */
ExecutorToken getMainExecutorToken() const; ExecutorToken getMainExecutorToken() const;
/**
* Registers the given JSExecutor which runs on the given MessageQueueThread
* with the Bridge. Part of this registration is transfering ownership of this
* JSExecutor to the Bridge for the duration of the registration.
*
* Returns a ExecutorToken which can be used to refer to this JSExecutor
* in the Bridge.
*/
ExecutorToken registerExecutor(
std::unique_ptr<JSExecutor> executor,
std::shared_ptr<MessageQueueThread> executorMessageQueueThread);
/**
* Unregisters a JSExecutor that was previously registered with this Bridge
* using registerExecutor. Use the ExecutorToken returned from this
* registerExecutor call. This method will return ownership of the unregistered
* executor to the caller for it to retain or tear down.
*
* Returns ownership of the unregistered executor.
*/
std::unique_ptr<JSExecutor> unregisterExecutor(ExecutorToken executorToken);
/** /**
* Synchronously tears down the bridge and the main executor. * Synchronously tears down the bridge and the main executor.
*/ */
void destroy(); void destroy();
private: private:
/**
* Registers the given JSExecutor which runs on the given MessageQueueThread
* with the NativeToJsBridge. Part of this registration is transfering
* ownership of this JSExecutor to the NativeToJsBridge for the duration of
* the registration.
*
* Returns a ExecutorToken which can be used to refer to this JSExecutor
* in the NativeToJsBridge.
*/
ExecutorToken registerExecutor(
ExecutorToken token,
std::unique_ptr<JSExecutor> executor,
std::shared_ptr<MessageQueueThread> executorMessageQueueThread);
/**
* Unregisters a JSExecutor that was previously registered with this NativeToJsBridge
* using registerExecutor.
*/
std::unique_ptr<JSExecutor> unregisterExecutor(JSExecutor& executorToken);
void runOnExecutorQueue(ExecutorToken token, std::function<void(JSExecutor*)> task); void runOnExecutorQueue(ExecutorToken token, std::function<void(JSExecutor*)> task);
std::unique_ptr<BridgeCallback> m_callback;
// This is used to avoid a race condition where a proxyCallback gets queued after ~Bridge(), // This is used to avoid a race condition where a proxyCallback gets queued
// on the same thread. In that case, the callback will try to run the task on m_callback which // after ~NativeToJsBridge(), on the same thread. In that case, the callback
// will have been destroyed within ~Bridge(), thus causing a SIGSEGV. // will try to run the task on m_callback which will have been destroyed
// within ~NativeToJsBridge(), thus causing a SIGSEGV.
std::shared_ptr<bool> m_destroyed; std::shared_ptr<bool> m_destroyed;
JSExecutor* m_mainExecutor; JSExecutor* m_mainExecutor;
std::unique_ptr<ExecutorToken> m_mainExecutorToken; ExecutorToken m_mainExecutorToken;
std::unique_ptr<ExecutorTokenFactory> m_executorTokenFactory; std::shared_ptr<JsToNativeBridge> m_delegate;
std::unordered_map<JSExecutor*, ExecutorToken> m_executorTokenMap; std::unordered_map<JSExecutor*, ExecutorToken> m_executorTokenMap;
std::unordered_map<ExecutorToken, std::unique_ptr<ExecutorRegistration>> m_executorMap; std::unordered_map<ExecutorToken, ExecutorRegistration> m_executorMap;
std::mutex m_registrationMutex; std::mutex m_registrationMutex;
#ifdef WITH_FBSYSTRACE #ifdef WITH_FBSYSTRACE
std::atomic_uint_least32_t m_systraceCookie = ATOMIC_VAR_INIT(); std::atomic_uint_least32_t m_systraceCookie = ATOMIC_VAR_INIT();
@ -167,7 +153,7 @@ private:
MessageQueueThread* getMessageQueueThread(const ExecutorToken& executorToken); MessageQueueThread* getMessageQueueThread(const ExecutorToken& executorToken);
JSExecutor* getExecutor(const ExecutorToken& executorToken); JSExecutor* getExecutor(const ExecutorToken& executorToken);
inline ExecutorToken getTokenForExecutor(JSExecutor& executor); ExecutorToken getTokenForExecutor(JSExecutor& executor);
}; };
} } } }