From 779314a413851e7ae3d9ff483ba6421f1eb33630 Mon Sep 17 00:00:00 2001 From: Marc Horowitz Date: Wed, 18 May 2016 12:46:01 -0700 Subject: [PATCH] Implement incremental module requires Differential Revision: D3234844 fbshipit-source-id: 24615528ad6a049aad7c2dbb7ce55e8b034c79e7 --- ReactCommon/bridge/Bridge.cpp | 277 -------------- ReactCommon/bridge/Executor.h | 53 ++- ReactCommon/bridge/Instance.cpp | 118 +----- ReactCommon/bridge/Instance.h | 17 +- ReactCommon/bridge/JSCExecutor.cpp | 74 +++- ReactCommon/bridge/JSCExecutor.h | 24 +- ReactCommon/bridge/ModuleRegistry.cpp | 113 ++++-- ReactCommon/bridge/ModuleRegistry.h | 7 +- ReactCommon/bridge/NativeModule.h | 5 - ReactCommon/bridge/NativeToJsBridge.cpp | 347 ++++++++++++++++++ .../bridge/{Bridge.h => NativeToJsBridge.h} | 104 +++--- 11 files changed, 605 insertions(+), 534 deletions(-) delete mode 100644 ReactCommon/bridge/Bridge.cpp create mode 100644 ReactCommon/bridge/NativeToJsBridge.cpp rename ReactCommon/bridge/{Bridge.h => NativeToJsBridge.h} (64%) diff --git a/ReactCommon/bridge/Bridge.cpp b/ReactCommon/bridge/Bridge.cpp deleted file mode 100644 index 9437657bd4..0000000000 --- a/ReactCommon/bridge/Bridge.cpp +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -#include "Bridge.h" - -#ifdef WITH_FBSYSTRACE -#include -using fbsystrace::FbSystraceAsyncFlow; -#endif - -#include -#include -#include - -#include "Platform.h" -#include "SystraceSection.h" - -namespace facebook { -namespace react { - -Bridge::Bridge( - JSExecutorFactory* jsExecutorFactory, - std::shared_ptr jsQueue, - std::unique_ptr executorTokenFactory, - std::unique_ptr callback) : - m_callback(std::move(callback)), - m_destroyed(std::make_shared(false)), - m_executorTokenFactory(std::move(executorTokenFactory)) { - std::unique_ptr mainExecutor = jsExecutorFactory->createJSExecutor(this, jsQueue); - // cached to avoid locked map lookup in the common case - m_mainExecutor = mainExecutor.get(); - m_mainExecutorToken = folly::make_unique(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 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 unbundle, - std::unique_ptr 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, - "", - systraceCookie); - #endif - - runOnExecutorQueue(executorToken, [callbackId, arguments, systraceCookie] (JSExecutor* executor) { - #ifdef WITH_FBSYSTRACE - FbSystraceAsyncFlow::end( - TRACE_TAG_REACT_CXX_BRIDGE, - "", - systraceCookie); - SystraceSection s("Bridge.invokeCallback"); - #endif - - executor->invokeCallback(callbackId, arguments); - }); -} - -void Bridge::setGlobalVariable(std::string propName, - std::unique_ptr 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 executor, - std::shared_ptr messageQueueThread) { - auto token = m_executorTokenFactory->createExecutorToken(); - - std::lock_guard 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(std::move(executor), std::move(messageQueueThread))); - - return token; -} - -std::unique_ptr Bridge::unregisterExecutor(ExecutorToken executorToken) { - std::unique_ptr executor; - - { - std::lock_guard 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 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 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 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 mainExecutor = unregisterExecutor(*m_mainExecutorToken); - mainExecutor->destroy(); - }); -} - -void Bridge::runOnExecutorQueue(ExecutorToken executorToken, std::function 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 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); - }); -} - -} } diff --git a/ReactCommon/bridge/Executor.h b/ReactCommon/bridge/Executor.h index d005701be9..08af931d51 100644 --- a/ReactCommon/bridge/Executor.h +++ b/ReactCommon/bridge/Executor.h @@ -7,26 +7,45 @@ #include #include +#include + #include "JSModulesUnbundle.h" -namespace folly { - -struct dynamic; - -} - namespace facebook { namespace react { -class Bridge; class JSExecutor; 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 executor, + std::shared_ptr queue) = 0; + virtual std::unique_ptr unregisterExecutor(JSExecutor& executor) = 0; + + virtual std::vector 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 { public: virtual std::unique_ptr createJSExecutor( - Bridge *bridge, std::shared_ptr jsQueue) = 0; - virtual ~JSExecutorFactory() {}; + std::shared_ptr delegate, + std::shared_ptr jsQueue) = 0; + virtual ~JSExecutorFactory() {} }; // JSExecutor functions sometimes take large strings, on the order of @@ -143,18 +162,18 @@ public: std::unique_ptr jsonValue) = 0; virtual void* getJavaScriptContext() { return nullptr; - }; + } virtual bool supportsProfiling() { return false; - }; - virtual void startProfiler(const std::string &titleString) {}; - virtual void stopProfiler(const std::string &titleString, const std::string &filename) {}; - virtual void handleMemoryPressureModerate() {}; + } + virtual void startProfiler(const std::string &titleString) {} + virtual void stopProfiler(const std::string &titleString, const std::string &filename) {} + virtual void handleMemoryPressureModerate() {} virtual void handleMemoryPressureCritical() { handleMemoryPressureModerate(); - }; - virtual void destroy() {}; - virtual ~JSExecutor() {}; + } + virtual void destroy() {} + virtual ~JSExecutor() {} }; } } diff --git a/ReactCommon/bridge/Instance.cpp b/ReactCommon/bridge/Instance.cpp index a5b435e15b..fa70b02abb 100644 --- a/ReactCommon/bridge/Instance.cpp +++ b/ReactCommon/bridge/Instance.cpp @@ -8,6 +8,7 @@ #include #include +#include #include @@ -19,42 +20,9 @@ namespace facebook { 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() { - if (nativeQueue_) { - nativeQueue_->quitSynchronous(); - bridge_->destroy(); + if (nativeToJsBridge_) { + nativeToJsBridge_->destroy(); } } @@ -65,32 +33,15 @@ void Instance::initializeBridge( std::unique_ptr nativeQueue, std::shared_ptr moduleRegistry) { callback_ = std::move(callback); - nativeQueue_ = std::move(nativeQueue); - jsQueue_ = jsQueue; - moduleRegistry_ = moduleRegistry; - jsQueue_->runOnQueueSync([this, &jsef] { - bridge_ = folly::make_unique( - jsef.get(), jsQueue_, folly::make_unique(callback_.get()), folly::make_unique(this)); - }); - SystraceSection s("setBatchedBridgeConfig"); + jsQueue->runOnQueueSync( + [this, &jsef, moduleRegistry, jsQueue, + nativeQueue=folly::makeMoveWrapper(std::move(nativeQueue))] () mutable { + nativeToJsBridge_ = folly::make_unique( + jsef.get(), moduleRegistry, jsQueue, nativeQueue.move(), callback_); + }); - CHECK(bridge_); - - 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(folly::toJson(config))); + CHECK(nativeToJsBridge_); } void Instance::loadScriptFromString(std::unique_ptr string, @@ -99,7 +50,7 @@ void Instance::loadScriptFromString(std::unique_ptr string, SystraceSection s("reactbridge_xplat_loadScriptFromString", "sourceURL", sourceURL); // 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, @@ -129,71 +80,42 @@ void Instance::loadUnbundle(std::unique_ptr unbundle, std::string startupScriptSourceURL) { callback_->incrementPendingJSCalls(); SystraceSection s("reactbridge_xplat_setJSModulesUnbundle"); - bridge_->loadApplicationUnbundle(std::move(unbundle), std::move(startupScript), - std::move(startupScriptSourceURL)); + nativeToJsBridge_->loadApplicationUnbundle(std::move(unbundle), std::move(startupScript), + std::move(startupScriptSourceURL)); } bool Instance::supportsProfiling() { - return bridge_->supportsProfiling(); + return nativeToJsBridge_->supportsProfiling(); } 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) { - return bridge_->stopProfiler(title, filename); + return nativeToJsBridge_->stopProfiler(title, filename); } void Instance::setGlobalVariable(std::string propName, std::unique_ptr 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, folly::dynamic&& params, const std::string& tracingName) { SystraceSection s(tracingName.c_str()); 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) { SystraceSection s(""); callback_->incrementPendingJSCalls(); - bridge_->invokeCallback(token, (double) callbackId, std::move(params)); + nativeToJsBridge_->invokeCallback(token, (double) callbackId, std::move(params)); } ExecutorToken Instance::getMainExecutorToken() { - return bridge_->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)); + return nativeToJsBridge_->getMainExecutorToken(); } } // namespace react diff --git a/ReactCommon/bridge/Instance.h b/ReactCommon/bridge/Instance.h index 53ec7ac198..9aee9d238b 100644 --- a/ReactCommon/bridge/Instance.h +++ b/ReactCommon/bridge/Instance.h @@ -6,7 +6,7 @@ #include -#include "Bridge.h" +#include "NativeToJsBridge.h" #include "ModuleRegistry.h" #include "NativeModule.h" @@ -22,6 +22,7 @@ struct InstanceCallback { virtual void decrementPendingJSCalls() = 0; virtual void onNativeException(const std::string& what) = 0; virtual ExecutorToken createExecutorToken() = 0; + virtual void onExecutorStopped(ExecutorToken) = 0; }; class Instance { @@ -46,21 +47,15 @@ class Instance { void callJSFunction(ExecutorToken token, const std::string& module, const std::string& method, folly::dynamic&& params, const std::string& tracingName); 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(); private: - class BridgeCallbackImpl; - void callNativeModules(ExecutorToken token, const std::string& calls, bool isEndOfBatch); - std::unique_ptr callback_; - std::shared_ptr moduleRegistry_; - // TODO #10487027: clean up the ownership of this. - std::shared_ptr jsQueue_; - std::unique_ptr nativeQueue_; - - std::unique_ptr bridge_; + std::shared_ptr callback_; + std::unique_ptr nativeToJsBridge_; }; } diff --git a/ReactCommon/bridge/JSCExecutor.cpp b/ReactCommon/bridge/JSCExecutor.cpp index ad14e99068..4bc0d99a04 100644 --- a/ReactCommon/bridge/JSCExecutor.cpp +++ b/ReactCommon/bridge/JSCExecutor.cpp @@ -14,7 +14,6 @@ #include #include -#include "Bridge.h" #include "JSCHelpers.h" #include "Platform.h" #include "SystraceSection.h" @@ -107,29 +106,51 @@ static std::string executeJSCallWithJSC( } std::unique_ptr JSCExecutorFactory::createJSExecutor( - Bridge *bridge, std::shared_ptr jsQueue) { + std::shared_ptr delegate, std::shared_ptr jsQueue) { return std::unique_ptr( - new JSCExecutor(bridge, jsQueue, cacheDir_, m_jscConfig)); + new JSCExecutor(delegate, jsQueue, m_cacheDir, m_jscConfig)); } -JSCExecutor::JSCExecutor(Bridge *bridge, std::shared_ptr messageQueueThread, - const std::string& cacheDir, const folly::dynamic& jscConfig) : - m_bridge(bridge), +JSCExecutor::JSCExecutor(std::shared_ptr delegate, + std::shared_ptr messageQueueThread, + const std::string& cacheDir, + const folly::dynamic& jscConfig) : + m_delegate(delegate), m_deviceCacheDir(cacheDir), m_messageQueueThread(messageQueueThread), m_jscConfig(jscConfig) { initOnJSVMThread(); + + SystraceSection s("setBatchedBridgeConfig"); + + folly::dynamic nativeModuleConfig = folly::dynamic::array(); + + { + SystraceSection s("collectNativeModuleNames"); + std::vector 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(folly::toJson(config))); } JSCExecutor::JSCExecutor( - Bridge *bridge, + std::shared_ptr delegate, std::shared_ptr messageQueueThread, int workerId, JSCExecutor *owner, std::string scriptURL, std::unordered_map globalObjAsJSON, const folly::dynamic& jscConfig) : - m_bridge(bridge), + m_delegate(delegate), m_workerId(workerId), m_owner(owner), 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 JSObjectSetPrivate(JSContextGetGlobalObject(m_context), this); + installNativeHook<&JSCExecutor::nativeRequireModuleConfig>("nativeRequireModuleConfig"); installNativeHook<&JSCExecutor::nativeFlushQueueImmediate>("nativeFlushQueueImmediate"); installNativeHook<&JSCExecutor::nativeStartWorker>("nativeStartWorker"); installNativeHook<&JSCExecutor::nativePostMessageToWorker>("nativePostMessageToWorker"); @@ -280,7 +302,7 @@ void JSCExecutor::setJSModulesUnbundle(std::unique_ptr unbund void JSCExecutor::flush() { // TODO: Make this a first class function instead of evaling. #9317773 std::string calls = executeJSCallWithJSC(m_context, "flushedQueue", std::vector()); - 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) { @@ -291,7 +313,7 @@ void JSCExecutor::callFunction(const std::string& moduleId, const std::string& m std::move(arguments), }; 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) { @@ -301,7 +323,7 @@ void JSCExecutor::invokeCallback(const double callbackId, const folly::dynamic& std::move(arguments) }; 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 jsonValue) { @@ -362,7 +384,7 @@ void JSCExecutor::handleMemoryPressureCritical() { } 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) { @@ -388,7 +410,7 @@ int JSCExecutor::addWebWorker( WebWorkerUtil::createWebWorkerThread(workerId, m_messageQueueThread.get()); std::unique_ptr worker; 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)); }); @@ -397,14 +419,14 @@ int JSCExecutor::addWebWorker( JSCExecutor *workerPtr = worker.get(); std::shared_ptr sharedMessageQueueThread = worker->m_messageQueueThread; - ExecutorToken token = m_bridge->registerExecutor( + m_delegate->registerExecutor( std::move(worker), std::move(sharedMessageQueueThread)); m_ownedWorkers.emplace( std::piecewise_construct, std::forward_as_tuple(workerId), - std::forward_as_tuple(workerPtr, token, std::move(workerObj))); + std::forward_as_tuple(workerPtr, std::move(workerObj))); return workerId; } @@ -464,12 +486,11 @@ void JSCExecutor::receiveMessageFromOwner(const std::string& msgString) { void JSCExecutor::terminateOwnedWebWorker(int workerId) { auto& workerRegistration = m_ownedWorkers.at(workerId); std::shared_ptr workerMQT = workerRegistration.executor->m_messageQueueThread; - ExecutorToken workerExecutorToken = workerRegistration.executorToken; m_ownedWorkers.erase(workerId); - workerMQT->runOnQueueSync([this, workerExecutorToken, &workerMQT] { + workerMQT->runOnQueueSync([this, &workerMQT] { workerMQT->quitSynchronous(); - std::unique_ptr worker = m_bridge->unregisterExecutor(workerExecutorToken); + std::unique_ptr worker = m_delegate->unregisterExecutor(*this); worker->destroy(); worker.reset(); }); @@ -521,6 +542,18 @@ JSValueRef JSCExecutor::nativeRequire( 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( size_t argumentCount, const JSValueRef arguments[]) { @@ -529,7 +562,7 @@ JSValueRef JSCExecutor::nativeFlushQueueImmediate( } std::string resStr = Value(m_context, arguments[0]).toJSONString(); - flushQueueImmediate(resStr); + flushQueueImmediate(std::move(resStr)); return JSValueMakeUndefined(m_context); } @@ -595,7 +628,8 @@ JSValueRef JSCExecutor::nativeCallSyncHook( unsigned int methodId = Value(m_context, arguments[1]).asUnsignedInteger(); std::string argsJson = Value(m_context, arguments[2]).toJSONString(); - MethodCallResult result = m_bridge->callSerializableNativeHook( + MethodCallResult result = m_delegate->callSerializableNativeHook( + *this, moduleId, methodId, argsJson); diff --git a/ReactCommon/bridge/JSCExecutor.h b/ReactCommon/bridge/JSCExecutor.h index a0aac1c277..e869946d76 100644 --- a/ReactCommon/bridge/JSCExecutor.h +++ b/ReactCommon/bridge/JSCExecutor.h @@ -23,25 +23,24 @@ class MessageQueueThread; class JSCExecutorFactory : public JSExecutorFactory { public: JSCExecutorFactory(const std::string& cacheDir, const folly::dynamic& jscConfig) : - cacheDir_(cacheDir), + m_cacheDir(cacheDir), m_jscConfig(jscConfig) {} virtual std::unique_ptr createJSExecutor( - Bridge *bridge, std::shared_ptr jsQueue) override; + std::shared_ptr delegate, + std::shared_ptr jsQueue) override; private: - std::string cacheDir_; + std::string m_cacheDir; folly::dynamic m_jscConfig; }; class JSCExecutor; class WorkerRegistration : public noncopyable { public: - explicit WorkerRegistration(JSCExecutor* executor_, ExecutorToken executorToken_, Object jsObj_) : + explicit WorkerRegistration(JSCExecutor* executor_, Object jsObj_) : executor(executor_), - executorToken(executorToken_), jsObj(std::move(jsObj_)) {} JSCExecutor *executor; - ExecutorToken executorToken; Object jsObj; }; @@ -50,8 +49,10 @@ public: /** * Must be invoked from thread this Executor will run on. */ - explicit JSCExecutor(Bridge *bridge, std::shared_ptr messageQueueThread, - const std::string& cacheDir, const folly::dynamic& jscConfig); + explicit JSCExecutor(std::shared_ptr delegate, + std::shared_ptr messageQueueThread, + const std::string& cacheDir, + const folly::dynamic& jscConfig); ~JSCExecutor() override; virtual void loadApplicationScript( @@ -79,7 +80,7 @@ public: private: JSGlobalContextRef m_context; - Bridge *m_bridge; + std::shared_ptr m_delegate; 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 std::shared_ptr m_isDestroyed = std::shared_ptr(new bool(false)); @@ -93,7 +94,7 @@ private: * WebWorker constructor. Must be invoked from thread this Executor will run on. */ JSCExecutor( - Bridge *bridge, + std::shared_ptr delegate, std::shared_ptr messageQueueThread, int workerId, JSCExecutor *owner, @@ -118,6 +119,9 @@ private: template< JSValueRef (JSCExecutor::*method)(size_t, const JSValueRef[])> void installNativeHook(const char* name); + JSValueRef nativeRequireModuleConfig( + size_t argumentCount, + const JSValueRef arguments[]); JSValueRef nativeStartWorker( size_t argumentCount, const JSValueRef arguments[]); diff --git a/ReactCommon/bridge/ModuleRegistry.cpp b/ReactCommon/bridge/ModuleRegistry.cpp index 18ee5f1782..6cd6671c1f 100644 --- a/ReactCommon/bridge/ModuleRegistry.cpp +++ b/ReactCommon/bridge/ModuleRegistry.cpp @@ -8,45 +8,86 @@ namespace facebook { 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> modules) : modules_(std::move(modules)) {} -folly::dynamic ModuleRegistry::moduleDescriptions() { - folly::dynamic modDescs = folly::dynamic::object; - - for (size_t moduleId = 0; moduleId < modules_.size(); ++moduleId) { - const auto& module = modules_[moduleId]; - - folly::dynamic methodDescs = folly::dynamic::object; - std::vector methods; - { - SystraceSection s("getMethods", - "module", module->getName()); - methods = module->getMethods(); - } - for (size_t methodId = 0; methodId < methods.size(); ++methodId) { - methodDescs.insert(std::move(methods[methodId].name), - folly::dynamic::object - ("methodID", methodId) - ("type", std::move(methods[methodId].type))); - } - - folly::dynamic constants = folly::dynamic::array(); - { - SystraceSection s("getConstants", - "module", module->getName()); - constants = module->getConstants(); - } - - modDescs.insert(module->getName(), - folly::dynamic::object - ("supportsWebWorkers", module->supportsWebWorkers()) - ("moduleID", moduleId) - ("methods", std::move(methodDescs)) - ("constants", std::move(constants))); - +std::vector ModuleRegistry::moduleNames() { + std::vector names; + for (size_t i = 0; i < modules_.size(); i++) { + std::string name = normalizeName(modules_[i]->getName()); + modulesByName_[name] = i; + names.push_back(std::move(name)); + } + return names; +} + +folly::dynamic ModuleRegistry::getConfig(const std::string& name) { + auto it = modulesByName_.find(name); + if (it == modulesByName_.end()) { + return nullptr; + } + CHECK(it->second < modules_.size()); + + NativeModule* module = modules_[it->second].get(); + + // string name, [object constants,] array methodNames (methodId is index), [array asyncMethodIds] + folly::dynamic config = folly::dynamic::array(name); + + { + SystraceSection s("getConfig constants", + "module", name); + folly::dynamic constants = module->getConstants(); + if (constants.isObject() && constants.size() > 0) { + config.push_back(std::move(constants)); + } + } + + { + SystraceSection s("getConfig methods", + "module", name); + std::vector 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, @@ -75,7 +116,7 @@ void ModuleRegistry::callNativeMethod(ExecutorToken token, unsigned int moduleId // fall through; } - std::string moduleName = modules_[moduleId]->getName(); + std::string moduleName = normalizeName(modules_[moduleId]->getName()); auto descs = modules_[moduleId]->getMethods(); std::string methodName; if (methodId < descs.size()) { diff --git a/ReactCommon/bridge/ModuleRegistry.h b/ReactCommon/bridge/ModuleRegistry.h index 2bc85b61b4..a96a20bcf8 100644 --- a/ReactCommon/bridge/ModuleRegistry.h +++ b/ReactCommon/bridge/ModuleRegistry.h @@ -25,13 +25,18 @@ class ModuleRegistry { // notifyCatalystInstanceDestroy: use RAII instead ModuleRegistry(std::vector> modules); - folly::dynamic moduleDescriptions(); + std::vector moduleNames(); + folly::dynamic getConfig(const std::string& name); void callNativeMethod(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& params, int callId); MethodCallResult callSerializableNativeHook(ExecutorToken token, unsigned int moduleId, unsigned int methodId, folly::dynamic&& args); private: + // This is always populated std::vector> modules_; + + // This is only populated if moduleNames() is called. Values are indices into modules_. + std::unordered_map modulesByName_; }; } diff --git a/ReactCommon/bridge/NativeModule.h b/ReactCommon/bridge/NativeModule.h index 6a71ef0d6e..cbc82aa46c 100644 --- a/ReactCommon/bridge/NativeModule.h +++ b/ReactCommon/bridge/NativeModule.h @@ -12,11 +12,6 @@ namespace facebook { namespace react { -struct MethodCallResult { - folly::dynamic result; - bool isUndefined; -}; - struct MethodDescriptor { std::string name; // type is one of js MessageQueue.MethodTypes diff --git a/ReactCommon/bridge/NativeToJsBridge.cpp b/ReactCommon/bridge/NativeToJsBridge.cpp new file mode 100644 index 0000000000..c7c0292698 --- /dev/null +++ b/ReactCommon/bridge/NativeToJsBridge.cpp @@ -0,0 +1,347 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "NativeToJsBridge.h" + +#ifdef WITH_FBSYSTRACE +#include +using fbsystrace::FbSystraceAsyncFlow; +#endif + +#include +#include +#include + +#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 registry, + std::unique_ptr nativeQueue, + std::shared_ptr callback) + : m_nativeToJs(nativeToJs) + , m_registry(registry) + , m_nativeQueue(std::move(nativeQueue)) + , m_callback(callback) {} + + void registerExecutor(std::unique_ptr executor, + std::shared_ptr queue) override { + m_nativeToJs->registerExecutor(m_callback->createExecutorToken(), std::move(executor), queue); + } + + std::unique_ptr unregisterExecutor(JSExecutor& executor) override { + m_callback->onExecutorStopped(m_nativeToJs->getTokenForExecutor(executor)); + return m_nativeToJs->unregisterExecutor(executor); + } + + std::vector 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 m_registry; + std::unique_ptr m_nativeQueue; + std::shared_ptr m_callback; +}; + +NativeToJsBridge::NativeToJsBridge( + JSExecutorFactory* jsExecutorFactory, + std::shared_ptr registry, + std::shared_ptr jsQueue, + std::unique_ptr nativeQueue, + std::shared_ptr callback) + : m_destroyed(std::make_shared(false)) + , m_mainExecutorToken(callback->createExecutorToken()) + , m_delegate( + std::make_shared( + this, registry, std::move(nativeQueue), callback)) { + std::unique_ptr 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 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 unbundle, + std::unique_ptr 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, + "", + systraceCookie); + #endif + + runOnExecutorQueue(executorToken, [callbackId, arguments, systraceCookie] (JSExecutor* executor) { + #ifdef WITH_FBSYSTRACE + FbSystraceAsyncFlow::end( + TRACE_TAG_REACT_CXX_BRIDGE, + "", + systraceCookie); + SystraceSection s("NativeToJsBridge.invokeCallback"); + #endif + + executor->invokeCallback(callbackId, arguments); + }); +} + +void NativeToJsBridge::setGlobalVariable(std::string propName, + std::unique_ptr 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 executor, + std::shared_ptr messageQueueThread) { + std::lock_guard 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 NativeToJsBridge::unregisterExecutor(JSExecutor& executor) { + std::unique_ptr ret; + + { + std::lock_guard 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 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 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 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 mainExecutor = unregisterExecutor(*m_mainExecutor); + m_mainExecutor = nullptr; + mainExecutor->destroy(); + }); +} + +void NativeToJsBridge::runOnExecutorQueue(ExecutorToken executorToken, std::function 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 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); + }); +} + +} } diff --git a/ReactCommon/bridge/Bridge.h b/ReactCommon/bridge/NativeToJsBridge.h similarity index 64% rename from ReactCommon/bridge/Bridge.h rename to ReactCommon/bridge/NativeToJsBridge.h index ebee2b8aaf..3f3230a6c9 100644 --- a/ReactCommon/bridge/Bridge.h +++ b/ReactCommon/bridge/NativeToJsBridge.h @@ -11,7 +11,6 @@ #include "Executor.h" #include "ExecutorToken.h" -#include "ExecutorTokenFactory.h" #include "JSModulesUnbundle.h" #include "MessageQueueThread.h" #include "MethodCall.h" @@ -27,21 +26,9 @@ struct dynamic; namespace facebook { namespace react { -class BridgeCallback { -public: - virtual ~BridgeCallback() {}; +struct InstanceCallback; +class ModuleRegistry; - 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 { public: ExecutorRegistration( @@ -54,17 +41,26 @@ public: std::shared_ptr 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: + friend class JsToNativeBridge; + /** * This must be called on the main JS thread. */ - Bridge( + NativeToJsBridge( JSExecutorFactory* jsExecutorFactory, + std::shared_ptr registry, std::shared_ptr jsQueue, - std::unique_ptr executorTokenFactory, - std::unique_ptr callback); - virtual ~Bridge(); + std::unique_ptr nativeQueue, + std::shared_ptr callback); + virtual ~NativeToJsBridge(); /** * Executes a function with the module ID and method ID and any additional @@ -108,58 +104,48 @@ public: void handleMemoryPressureModerate(); 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. */ 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 executor, - std::shared_ptr 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 unregisterExecutor(ExecutorToken executorToken); - /** * Synchronously tears down the bridge and the main executor. */ void destroy(); 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 executor, + std::shared_ptr executorMessageQueueThread); + + /** + * Unregisters a JSExecutor that was previously registered with this NativeToJsBridge + * using registerExecutor. + */ + std::unique_ptr unregisterExecutor(JSExecutor& executorToken); + void runOnExecutorQueue(ExecutorToken token, std::function task); - std::unique_ptr m_callback; - // This is used to avoid a race condition where a proxyCallback gets queued after ~Bridge(), - // on the same thread. In that case, the callback will try to run the task on m_callback which - // will have been destroyed within ~Bridge(), thus causing a SIGSEGV. + + // 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 + // within ~NativeToJsBridge(), thus causing a SIGSEGV. std::shared_ptr m_destroyed; JSExecutor* m_mainExecutor; - std::unique_ptr m_mainExecutorToken; - std::unique_ptr m_executorTokenFactory; + ExecutorToken m_mainExecutorToken; + std::shared_ptr m_delegate; std::unordered_map m_executorTokenMap; - std::unordered_map> m_executorMap; + std::unordered_map m_executorMap; std::mutex m_registrationMutex; #ifdef WITH_FBSYSTRACE std::atomic_uint_least32_t m_systraceCookie = ATOMIC_VAR_INIT(); @@ -167,7 +153,7 @@ private: MessageQueueThread* getMessageQueueThread(const ExecutorToken& executorToken); JSExecutor* getExecutor(const ExecutorToken& executorToken); - inline ExecutorToken getTokenForExecutor(JSExecutor& executor); + ExecutorToken getTokenForExecutor(JSExecutor& executor); }; } }