From c32e5fd84f6e4859e4039eda2ff654565ccdd3cd Mon Sep 17 00:00:00 2001 From: Andy Street Date: Fri, 26 Feb 2016 05:55:56 -0800 Subject: [PATCH] Revert D2926896 WebWorkers: Move web worker impl to JSCExecutor Reviewed By: lexs Differential Revision: D2982150 fb-gh-sync-id: c75d05988df50b9788608e7c1bf00c4952ccfce1 shipit-source-id: c75d05988df50b9788608e7c1bf00c4952ccfce1 --- .../react/bridge/CatalystInstanceImpl.java | 20 +- .../common/futures/SimpleSettableFuture.java | 2 +- ReactAndroid/src/main/jni/react/Android.mk | 1 + ReactAndroid/src/main/jni/react/BUCK | 2 + .../src/main/jni/react/JSCExecutor.cpp | 184 ++++-------------- ReactAndroid/src/main/jni/react/JSCExecutor.h | 65 ++----- .../src/main/jni/react/JSCWebWorker.cpp | 153 +++++++++++++++ .../src/main/jni/react/JSCWebWorker.h | 94 +++++++++ .../src/main/jni/react/MessageQueueThread.h | 20 -- 9 files changed, 312 insertions(+), 229 deletions(-) create mode 100644 ReactAndroid/src/main/jni/react/JSCWebWorker.cpp create mode 100644 ReactAndroid/src/main/jni/react/JSCWebWorker.h diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index cbf259f29..0b3476cb9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -226,18 +226,24 @@ public class CatalystInstanceImpl implements CatalystInstance { listener.onTransitionToBridgeIdle(); } } + + Systrace.unregisterListener(mTraceListener); + + // We can access the Bridge from any thread now because we know either we are on the JS thread + // or the JS thread has finished via ReactQueueConfiguration#destroy() + mBridge.dispose(); } private void synchronouslyDisposeBridgeOnJSThread() { final SimpleSettableFuture bridgeDisposeFuture = new SimpleSettableFuture<>(); mReactQueueConfiguration.getJSQueueThread().runOnQueue( - new Runnable() { - @Override - public void run() { - mBridge.dispose(); - bridgeDisposeFuture.set(null); - } - }); + new Runnable() { + @Override + public void run() { + mBridge.dispose(); + bridgeDisposeFuture.set(null); + } + }); bridgeDisposeFuture.getOrThrow(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java b/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java index 9caf34cd6..55fc9eb08 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java +++ b/ReactAndroid/src/main/java/com/facebook/react/common/futures/SimpleSettableFuture.java @@ -30,7 +30,7 @@ public class SimpleSettableFuture implements Future { * Sets the result. If another thread has called {@link #get}, they will immediately receive the * value. set or setException must only be called once. */ - public void set(@Nullable T result) { + public void set(T result) { checkNotSet(); mResult = result; mReadyLatch.countDown(); diff --git a/ReactAndroid/src/main/jni/react/Android.mk b/ReactAndroid/src/main/jni/react/Android.mk index f72729bdc..cafcb591b 100644 --- a/ReactAndroid/src/main/jni/react/Android.mk +++ b/ReactAndroid/src/main/jni/react/Android.mk @@ -8,6 +8,7 @@ LOCAL_SRC_FILES := \ Bridge.cpp \ JSCExecutor.cpp \ JSCHelpers.cpp \ + JSCWebWorker.cpp \ MethodCall.cpp \ Platform.cpp \ Value.cpp \ diff --git a/ReactAndroid/src/main/jni/react/BUCK b/ReactAndroid/src/main/jni/react/BUCK index f9ac1edb5..e397d252c 100644 --- a/ReactAndroid/src/main/jni/react/BUCK +++ b/ReactAndroid/src/main/jni/react/BUCK @@ -57,6 +57,7 @@ react_library( 'JSCTracing.cpp', 'JSCMemory.cpp', 'JSCLegacyProfiler.cpp', + 'JSCWebWorker.cpp', 'Platform.cpp', ], headers = [ @@ -70,6 +71,7 @@ react_library( 'Executor.h', 'JSCExecutor.h', 'JSCHelpers.h', + 'JSCWebWorker.h', 'MessageQueueThread.h', 'MethodCall.h', 'JSModulesUnbundle.h', diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp index ad9f3c05a..bc91fd103 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.cpp +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.cpp @@ -3,8 +3,7 @@ #include "JSCExecutor.h" #include -#include -#include +#include #include #include #include @@ -91,52 +90,6 @@ JSCExecutor::JSCExecutor(Bridge *bridge, const std::string& cacheDir, const foll m_deviceCacheDir(cacheDir), m_messageQueueThread(MessageQueues::getCurrentMessageQueueThread()), m_jscConfig(jscConfig) { - initOnJSVMThread(); -} - -JSCExecutor::JSCExecutor( - Bridge *bridge, - int workerId, - JSCExecutor *owner, - const std::string& script, - const std::unordered_map& globalObjAsJSON, - const folly::dynamic& jscConfig) : - m_bridge(bridge), - m_workerId(workerId), - m_owner(owner), - m_deviceCacheDir(owner->m_deviceCacheDir), - m_messageQueueThread(MessageQueues::getCurrentMessageQueueThread()), - m_jscConfig(jscConfig) { - // We post initOnJSVMThread here so that the owner doesn't have to wait for - // initialization on its own thread - m_messageQueueThread->runOnQueue([this, script, globalObjAsJSON] () { - initOnJSVMThread(); - - installGlobalFunction(m_context, "postMessage", nativePostMessage); - - for (auto& it : globalObjAsJSON) { - setGlobalVariable(it.first, it.second); - } - - // TODO(9604438): Protect against script does not exist - std::string scriptSrc = WebWorkerUtil::loadScriptFromAssets(script); - // TODO(9994180): Throw on error - loadApplicationScript(scriptSrc, script); - }); -} - -JSCExecutor::~JSCExecutor() { - *m_isDestroyed = true; - if (m_messageQueueThread->isOnThread()) { - terminateOnJSVMThread(); - } else { - m_messageQueueThread->runOnQueueSync([this] () { - terminateOnJSVMThread(); - }); - } -} - -void JSCExecutor::initOnJSVMThread() { m_context = JSGlobalContextCreateInGroup(nullptr, nullptr); s_globalContextRefToJSCExecutor[m_context] = this; installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate); @@ -165,20 +118,18 @@ void JSCExecutor::initOnJSVMThread() { #endif } -void JSCExecutor::terminateOnJSVMThread() { - // terminateOwnedWebWorker mutates m_ownedWorkers so collect all the workers - // to terminate first +JSCExecutor::~JSCExecutor() { + // terminateWebWorker mutates m_webWorkers so collect all the workers to terminate first std::vector workerIds; - for (auto& it : m_ownedWorkers) { - workerIds.push_back(it.first); + for (auto it = m_webWorkers.begin(); it != m_webWorkers.end(); it++) { + workerIds.push_back(it->first); } for (int workerId : workerIds) { - terminateOwnedWebWorker(workerId); + terminateWebWorker(workerId); } s_globalContextRefToJSCExecutor.erase(m_context); JSGlobalContextRelease(m_context); - m_context = nullptr; } void JSCExecutor::loadApplicationScript( @@ -215,10 +166,6 @@ void JSCExecutor::loadApplicationUnbundle( } void JSCExecutor::flush() { - if (m_owner != nullptr) { - // Web workers don't support native modules yet - return; - } // TODO: Make this a first class function instead of evaling. #9317773 std::string calls = executeJSCallWithJSC(m_context, "flushedQueue", std::vector()); m_bridge->callNativeModules(calls, true); @@ -310,114 +257,56 @@ void JSCExecutor::loadModule(uint32_t moduleId) { evaluateScript(m_context, source, sourceUrl); } -int JSCExecutor::addWebWorker( - const std::string& script, - JSValueRef workerRef, - JSValueRef globalObjRef) { - static std::atomic_int nextWorkerId(1); - int workerId = nextWorkerId++; +// WebWorker impl - Object globalObj = Value(m_context, globalObjRef).asObject(); - - auto workerMQT = WebWorkerUtil::createWebWorkerThread(workerId, m_messageQueueThread.get()); - std::unique_ptr worker; - workerMQT->runOnQueueSync([this, &worker, &script, &globalObj, workerId] () { - worker.reset(new JSCExecutor(m_bridge, workerId, this, script, globalObj.toJSONMap(), m_jscConfig)); - }); - - Object workerObj = Value(m_context, workerRef).asObject(); - workerObj.makeProtected(); - - m_ownedWorkers.emplace(std::piecewise_construct, std::forward_as_tuple(workerId), std::forward_as_tuple(std::move(worker), std::move(workerObj))); - - return workerId; +JSGlobalContextRef JSCExecutor::getContext() { + return m_context; } -void JSCExecutor::postMessageToOwnedWebWorker(int workerId, JSValueRef message, JSValueRef *exn) { - auto worker = m_ownedWorkers.at(workerId).getExecutor(); - std::string msgString = Value(m_context, message).toJSONString(); - - std::shared_ptr isWorkerDestroyed = worker->m_isDestroyed; - worker->m_messageQueueThread->runOnQueue([isWorkerDestroyed, worker, msgString] () { - if (*isWorkerDestroyed) { - return; - } - worker->receiveMessageFromOwner(msgString); - }); +std::shared_ptr JSCExecutor::getMessageQueueThread() { + return m_messageQueueThread; } -void JSCExecutor::postMessageToOwner(JSValueRef msg) { - std::string msgString = Value(m_context, msg).toJSONString(); - std::shared_ptr ownerIsDestroyed = m_owner->m_isDestroyed; - m_owner->m_messageQueueThread->runOnQueue([workerId=m_workerId, owner=m_owner, ownerIsDestroyed, msgString] () { - if (*ownerIsDestroyed) { - return; - } - owner->receiveMessageFromOwnedWebWorker(workerId, msgString); - }); -} +void JSCExecutor::onMessageReceived(int workerId, const std::string& json) { + Object& worker = m_webWorkerJSObjs.at(workerId); -void JSCExecutor::receiveMessageFromOwnedWebWorker(int workerId, const std::string& json) { - Object* workerObj; - try { - workerObj = &m_ownedWorkers.at(workerId).jsObj; - } catch (std::out_of_range& e) { - // Worker was already terminated - return; - } - - Value onmessageValue = workerObj->getProperty("onmessage"); + Value onmessageValue = worker.getProperty("onmessage"); if (onmessageValue.isUndefined()) { return; } - JSValueRef args[] = { createMessageObject(json) }; + JSValueRef args[] = { JSCWebWorker::createMessageObject(m_context, json) }; onmessageValue.asObject().callAsFunction(1, args); flush(); } -void JSCExecutor::receiveMessageFromOwner(const std::string& msgString) { - CHECK(m_owner) << "Received message in a Executor that doesn't have an owner!"; +int JSCExecutor::addWebWorker(const std::string& script, JSValueRef workerRef) { + static std::atomic_int nextWorkerId(0); + int workerId = nextWorkerId++; - JSValueRef args[] = { createMessageObject(msgString) }; - Value onmessageValue = Object::getGlobalObject(m_context).getProperty("onmessage"); - onmessageValue.asObject().callAsFunction(1, args); + m_webWorkers.emplace(std::piecewise_construct, std::forward_as_tuple(workerId), std::forward_as_tuple(workerId, this, script)); + Object workerObj = Value(m_context, workerRef).asObject(); + workerObj.makeProtected(); + m_webWorkerJSObjs.emplace(workerId, std::move(workerObj)); + return workerId; } -void JSCExecutor::terminateOwnedWebWorker(int workerId) { - auto worker = m_ownedWorkers.at(workerId).getExecutor(); - std::shared_ptr workerMQT = worker->m_messageQueueThread; - m_ownedWorkers.erase(workerId); - workerMQT->quitSynchronous(); +void JSCExecutor::postMessageToWebWorker(int workerId, JSValueRef message, JSValueRef *exn) { + JSCWebWorker& worker = m_webWorkers.at(workerId); + worker.postMessage(message); } -Object JSCExecutor::createMessageObject(const std::string& msgJson) { - Value rebornJSMsg = Value::fromJSON(m_context, String(msgJson.c_str())); - Object messageObject = Object::create(m_context); - messageObject.setProperty("data", rebornJSMsg); - return messageObject; +void JSCExecutor::terminateWebWorker(int workerId) { + JSCWebWorker& worker = m_webWorkers.at(workerId); + + worker.terminate(); + + m_webWorkers.erase(workerId); + m_webWorkerJSObjs.erase(workerId); } // Native JS hooks -JSValueRef JSCExecutor::nativePostMessage( - JSContextRef ctx, - JSObjectRef function, - JSObjectRef thisObject, - size_t argumentCount, - const JSValueRef arguments[], - JSValueRef *exception) { - if (argumentCount != 1) { - *exception = makeJSCException(ctx, "postMessage got wrong number of arguments"); - return JSValueMakeUndefined(ctx); - } - JSValueRef msg = arguments[0]; - JSCExecutor *webWorker = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx)); - - webWorker->postMessageToOwner(msg); - - return JSValueMakeUndefined(ctx); -} static JSValueRef makeInvalidModuleIdJSCException( JSContextRef ctx, @@ -508,7 +397,6 @@ JSValueRef JSCExecutor::nativeStartWorker( std::string scriptFile = Value(ctx, arguments[0]).toString().str(); JSValueRef worker = arguments[1]; - JSValueRef globalObj = arguments[2]; JSCExecutor *executor; try { @@ -518,7 +406,7 @@ JSValueRef JSCExecutor::nativeStartWorker( return JSValueMakeUndefined(ctx); } - int workerId = executor->addWebWorker(scriptFile, worker, globalObj); + int workerId = executor->addWebWorker(scriptFile, worker); return JSValueMakeNumber(ctx, workerId); } @@ -549,7 +437,7 @@ JSValueRef JSCExecutor::nativePostMessageToWorker( return JSValueMakeUndefined(ctx); } - executor->postMessageToOwnedWebWorker((int) workerDouble, arguments[1], exception); + executor->postMessageToWebWorker((int) workerDouble, arguments[1], exception); return JSValueMakeUndefined(ctx); } @@ -580,7 +468,7 @@ JSValueRef JSCExecutor::nativeTerminateWorker( return JSValueMakeUndefined(ctx); } - executor->terminateOwnedWebWorker((int) workerDouble); + executor->terminateWebWorker((int) workerDouble); return JSValueMakeUndefined(ctx); } diff --git a/ReactAndroid/src/main/jni/react/JSCExecutor.h b/ReactAndroid/src/main/jni/react/JSCExecutor.h index 336220144..3084a7195 100644 --- a/ReactAndroid/src/main/jni/react/JSCExecutor.h +++ b/ReactAndroid/src/main/jni/react/JSCExecutor.h @@ -7,10 +7,9 @@ #include #include #include - #include "Executor.h" #include "JSCHelpers.h" -#include "Value.h" +#include "JSCWebWorker.h" namespace facebook { namespace react { @@ -28,26 +27,10 @@ private: folly::dynamic m_jscConfig; }; -class JSCExecutor; -class WorkerRegistration : public noncopyable { -public: - explicit WorkerRegistration(std::unique_ptr executor, Object jsObj) : - jsObj(std::move(jsObj)), - executor(std::move(executor)) {} - - JSCExecutor* getExecutor() { - return executor.get(); - } - - Object jsObj; -private: - std::unique_ptr executor; -}; - -class JSCExecutor : public JSExecutor { +class JSCExecutor : public JSExecutor, public JSCWebWorkerOwner { public: /** - * Must be invoked from thread this Executor will run on. + * Should be invoked from the JS thread. */ explicit JSCExecutor(Bridge *bridge, const std::string& cacheDir, const folly::dynamic& jscConfig); ~JSCExecutor() override; @@ -77,43 +60,26 @@ public: virtual void handleMemoryPressureCritical() override; void installNativeHook(const char *name, JSObjectCallAsFunctionCallback callback); + virtual void onMessageReceived(int workerId, const std::string& message) override; + virtual JSGlobalContextRef getContext() override; + virtual std::shared_ptr getMessageQueueThread() override; private: JSGlobalContextRef m_context; + std::unordered_map m_webWorkers; + std::unordered_map m_webWorkerJSObjs; Bridge *m_bridge; - 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)); - std::unordered_map m_ownedWorkers; std::string m_deviceCacheDir; std::shared_ptr m_messageQueueThread; std::unique_ptr m_unbundle; folly::dynamic m_jscConfig; - /** - * WebWorker constructor. Must be invoked from thread this Executor will run on. - */ - explicit JSCExecutor( - Bridge *bridge, - int workerId, - JSCExecutor *owner, - const std::string& script, - const std::unordered_map& globalObjAsJSON, - const folly::dynamic& jscConfig); - - void initOnJSVMThread(); - void terminateOnJSVMThread(); + int addWebWorker(const std::string& script, JSValueRef workerRef); + void postMessageToWebWorker(int worker, JSValueRef message, JSValueRef *exn); void flush(); - void flushQueueImmediate(std::string queueJSON); + void terminateWebWorker(int worker); void loadModule(uint32_t moduleId); - - int addWebWorker(const std::string& script, JSValueRef workerRef, JSValueRef globalObjRef); - void postMessageToOwnedWebWorker(int worker, JSValueRef message, JSValueRef *exn); - void postMessageToOwner(JSValueRef result); - void receiveMessageFromOwnedWebWorker(int workerId, const std::string& message); - void receiveMessageFromOwner(const std::string &msgString); - void terminateOwnedWebWorker(int worker); - Object createMessageObject(const std::string& msgData); + void flushQueueImmediate(std::string queueJSON); static JSValueRef nativeStartWorker( JSContextRef ctx, @@ -136,13 +102,6 @@ private: size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception); - static JSValueRef nativePostMessage( - JSContextRef ctx, - JSObjectRef function, - JSObjectRef thisObject, - size_t argumentCount, - const JSValueRef arguments[], - JSValueRef *exception); static JSValueRef nativeRequire( JSContextRef ctx, JSObjectRef function, diff --git a/ReactAndroid/src/main/jni/react/JSCWebWorker.cpp b/ReactAndroid/src/main/jni/react/JSCWebWorker.cpp new file mode 100644 index 000000000..e8e710bdf --- /dev/null +++ b/ReactAndroid/src/main/jni/react/JSCWebWorker.cpp @@ -0,0 +1,153 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include "JSCWebWorker.h" + +#include +#include +#include +#include + +#include +#include + +#include "JSCHelpers.h" +#include "MessageQueueThread.h" +#include "Platform.h" +#include "Value.h" + +#include + +namespace facebook { +namespace react { + +// TODO(9604425): thread safety +static std::unordered_map s_globalContextRefToJSCWebWorker; + +JSCWebWorker::JSCWebWorker(int id, JSCWebWorkerOwner *owner, std::string scriptSrc) : + id_(id), + scriptName_(std::move(scriptSrc)), + owner_(owner) { + ownerMessageQueueThread_ = owner->getMessageQueueThread(); + CHECK(ownerMessageQueueThread_) << "Owner MessageQueue must not be null"; + workerMessageQueueThread_ = WebWorkerUtil::createWebWorkerThread(id, ownerMessageQueueThread_.get()); + CHECK(workerMessageQueueThread_) << "Failed to create worker thread"; + + workerMessageQueueThread_->runOnQueue([this] () { + initJSVMAndLoadScript(); + }); +} + + +JSCWebWorker::~JSCWebWorker() { + CHECK(isTerminated()) << "Didn't terminate the web worker before releasing it!";; +} + +void JSCWebWorker::postMessage(JSValueRef msg) { + std::string msgString = Value(owner_->getContext(), msg).toJSONString(); + + workerMessageQueueThread_->runOnQueue([this, msgString] () { + if (isTerminated()) { + return; + } + + JSValueRef args[] = { createMessageObject(context_, msgString) }; + Value onmessageValue = Object::getGlobalObject(context_).getProperty("onmessage"); + onmessageValue.asObject().callAsFunction(1, args); + }); +} + +void JSCWebWorker::terminate() { + if (isTerminated()) { + return; + } + isTerminated_.store(true, std::memory_order_release); + + if (workerMessageQueueThread_->isOnThread()) { + terminateOnWorkerThread(); + } else { + std::mutex signalMutex; + std::condition_variable signalCv; + bool terminationComplete = false; + + workerMessageQueueThread_->runOnQueue([&] () mutable { + std::lock_guard lock(signalMutex); + + terminateOnWorkerThread(); + terminationComplete = true; + + signalCv.notify_one(); + }); + + std::unique_lock lock(signalMutex); + signalCv.wait(lock, [&terminationComplete] { return terminationComplete; }); + } +} + +void JSCWebWorker::terminateOnWorkerThread() { + s_globalContextRefToJSCWebWorker.erase(context_); + JSGlobalContextRelease(context_); + context_ = nullptr; + workerMessageQueueThread_->quitSynchronous(); +} + +bool JSCWebWorker::isTerminated() { + return isTerminated_.load(std::memory_order_acquire); +} + +void JSCWebWorker::initJSVMAndLoadScript() { + CHECK(!isTerminated()) << "Worker was already finished!"; + CHECK(!context_) << "Worker JS VM was already created!"; + + context_ = JSGlobalContextCreateInGroup( + NULL, // use default JS 'global' object + NULL // create new group (i.e. new VM) + ); + s_globalContextRefToJSCWebWorker[context_] = this; + + // TODO(9604438): Protect against script does not exist + std::string script = WebWorkerUtil::loadScriptFromAssets(scriptName_); + evaluateScript(context_, String(script.c_str()), String(scriptName_.c_str())); + + installGlobalFunction(context_, "postMessage", nativePostMessage); +} + +void JSCWebWorker::postMessageToOwner(JSValueRef msg) { + std::string msgString = Value(context_, msg).toJSONString(); + ownerMessageQueueThread_->runOnQueue([this, msgString] () { + owner_->onMessageReceived(id_, msgString); + }); +} + +JSValueRef JSCWebWorker::nativePostMessage( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef *exception) { + if (argumentCount != 1) { + *exception = makeJSCException(ctx, "postMessage got wrong number of arguments"); + return JSValueMakeUndefined(ctx); + } + JSValueRef msg = arguments[0]; + JSCWebWorker *webWorker = s_globalContextRefToJSCWebWorker.at(JSContextGetGlobalContext(ctx)); + + if (webWorker->isTerminated()) { + return JSValueMakeUndefined(ctx); + } + + webWorker->postMessageToOwner(msg); + + return JSValueMakeUndefined(ctx); +} + +/*static*/ +Object JSCWebWorker::createMessageObject(JSContextRef context, const std::string& msgJson) { + Value rebornJSMsg = Value::fromJSON(context, String(msgJson.c_str())); + Object messageObject = Object::create(context); + messageObject.setProperty("data", rebornJSMsg); + return std::move(messageObject); +} + +} +} diff --git a/ReactAndroid/src/main/jni/react/JSCWebWorker.h b/ReactAndroid/src/main/jni/react/JSCWebWorker.h new file mode 100644 index 000000000..73d983cdf --- /dev/null +++ b/ReactAndroid/src/main/jni/react/JSCWebWorker.h @@ -0,0 +1,94 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +#include +#include +#include +#include +#include +#include + +#include + +#include "Value.h" + +namespace facebook { +namespace react { + +class MessageQueueThread; + +/** + * A class that can own the lifecycle, receive messages from, and dispatch messages + * to JSCWebWorkers. + */ +class JSCWebWorkerOwner { +public: + /** + * Called when a worker has posted a message with `postMessage`. + */ + virtual void onMessageReceived(int workerId, const std::string& message) = 0; + virtual JSGlobalContextRef getContext() = 0; + + /** + * Should return the owner's MessageQueueThread. Calls to onMessageReceived will be enqueued + * on this thread. + */ + virtual std::shared_ptr getMessageQueueThread() = 0; +}; + +/** + * Implementation of a web worker for JSC. The web worker should be created from the owner's + * (e.g., owning JSCExecutor instance) JS MessageQueueThread. The worker is responsible for + * creating its own MessageQueueThread. + * + * During operation, the JSCExecutor should call postMessage **from its own MessageQueueThread** + * to send messages to the worker. The worker will handle enqueueing those messages on its own + * MessageQueueThread as appropriate. When the worker has a message to post to the owner, it will + * enqueue a call to owner->onMessageReceived on the owner's MessageQueueThread. + */ +class JSCWebWorker { +public: + explicit JSCWebWorker(int id, JSCWebWorkerOwner *owner, std::string script); + ~JSCWebWorker(); + + /** + * Post a message to be received by the worker on its thread. This must be called from + * ownerMessageQueueThread_. + */ + void postMessage(JSValueRef msg); + + /** + * Synchronously quits the current worker and cleans up its VM. + */ + void terminate(); + + /** + * Whether terminate() has been called on this worker. + */ + bool isTerminated(); + + static Object createMessageObject(JSContextRef context, const std::string& msgData); +private: + void initJSVMAndLoadScript(); + void postRunnableToEventLoop(std::function&& runnable); + void postMessageToOwner(JSValueRef result); + void terminateOnWorkerThread(); + + int id_; + std::atomic_bool isTerminated_ = ATOMIC_VAR_INIT(false); + std::string scriptName_; + JSCWebWorkerOwner *owner_ = nullptr; + std::shared_ptr ownerMessageQueueThread_; + std::unique_ptr workerMessageQueueThread_; + JSGlobalContextRef context_ = nullptr; + + static JSValueRef nativePostMessage( + JSContextRef ctx, + JSObjectRef function, + JSObjectRef thisObject, + size_t argumentCount, + const JSValueRef arguments[], + JSValueRef *exception); +}; + +} +} diff --git a/ReactAndroid/src/main/jni/react/MessageQueueThread.h b/ReactAndroid/src/main/jni/react/MessageQueueThread.h index f6352ab0d..544b91142 100644 --- a/ReactAndroid/src/main/jni/react/MessageQueueThread.h +++ b/ReactAndroid/src/main/jni/react/MessageQueueThread.h @@ -2,9 +2,7 @@ #pragma once -#include #include -#include namespace facebook { namespace react { @@ -16,24 +14,6 @@ class MessageQueueThread { virtual bool isOnThread() = 0; // quitSynchronous() should synchronously ensure that no further tasks will run on the queue. virtual void quitSynchronous() = 0; - - void runOnQueueSync(std::function&& runnable) { - std::mutex signalMutex; - std::condition_variable signalCv; - bool runnableComplete = false; - - runOnQueue([&] () mutable { - std::lock_guard lock(signalMutex); - - runnable(); - runnableComplete = true; - - signalCv.notify_one(); - }); - - std::unique_lock lock(signalMutex); - signalCv.wait(lock, [&runnableComplete] { return runnableComplete; }); - } }; }}