Revert D2926896 WebWorkers: Move web worker impl to JSCExecutor
Reviewed By: lexs Differential Revision: D2982150 fb-gh-sync-id: c75d05988df50b9788608e7c1bf00c4952ccfce1 shipit-source-id: c75d05988df50b9788608e7c1bf00c4952ccfce1
This commit is contained in:
parent
50141f9d3f
commit
c32e5fd84f
|
@ -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<Void> 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ public class SimpleSettableFuture<T> implements Future<T> {
|
|||
* 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();
|
||||
|
|
|
@ -8,6 +8,7 @@ LOCAL_SRC_FILES := \
|
|||
Bridge.cpp \
|
||||
JSCExecutor.cpp \
|
||||
JSCHelpers.cpp \
|
||||
JSCWebWorker.cpp \
|
||||
MethodCall.cpp \
|
||||
Platform.cpp \
|
||||
Value.cpp \
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
#include "JSCExecutor.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <glog/logging.h>
|
||||
|
@ -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<std::string, std::string>& 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<int> 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<folly::dynamic>());
|
||||
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<JSCExecutor> 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<bool> isWorkerDestroyed = worker->m_isDestroyed;
|
||||
worker->m_messageQueueThread->runOnQueue([isWorkerDestroyed, worker, msgString] () {
|
||||
if (*isWorkerDestroyed) {
|
||||
return;
|
||||
}
|
||||
worker->receiveMessageFromOwner(msgString);
|
||||
});
|
||||
std::shared_ptr<MessageQueueThread> JSCExecutor::getMessageQueueThread() {
|
||||
return m_messageQueueThread;
|
||||
}
|
||||
|
||||
void JSCExecutor::postMessageToOwner(JSValueRef msg) {
|
||||
std::string msgString = Value(m_context, msg).toJSONString();
|
||||
std::shared_ptr<bool> 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<MessageQueueThread> 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);
|
||||
}
|
||||
|
|
|
@ -7,10 +7,9 @@
|
|||
#include <unordered_map>
|
||||
#include <folly/json.h>
|
||||
#include <JavaScriptCore/JSContextRef.h>
|
||||
|
||||
#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<JSCExecutor> executor, Object jsObj) :
|
||||
jsObj(std::move(jsObj)),
|
||||
executor(std::move(executor)) {}
|
||||
|
||||
JSCExecutor* getExecutor() {
|
||||
return executor.get();
|
||||
}
|
||||
|
||||
Object jsObj;
|
||||
private:
|
||||
std::unique_ptr<JSCExecutor> 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<MessageQueueThread> getMessageQueueThread() override;
|
||||
|
||||
private:
|
||||
JSGlobalContextRef m_context;
|
||||
std::unordered_map<int, JSCWebWorker> m_webWorkers;
|
||||
std::unordered_map<int, Object> 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<bool> m_isDestroyed = std::shared_ptr<bool>(new bool(false));
|
||||
std::unordered_map<int, WorkerRegistration> m_ownedWorkers;
|
||||
std::string m_deviceCacheDir;
|
||||
std::shared_ptr<MessageQueueThread> m_messageQueueThread;
|
||||
std::unique_ptr<JSModulesUnbundle> 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<std::string, std::string>& 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,
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include "JSCWebWorker.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <glog/logging.h>
|
||||
#include <folly/Memory.h>
|
||||
|
||||
#include "JSCHelpers.h"
|
||||
#include "MessageQueueThread.h"
|
||||
#include "Platform.h"
|
||||
#include "Value.h"
|
||||
|
||||
#include <JavaScriptCore/JSValueRef.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
// TODO(9604425): thread safety
|
||||
static std::unordered_map<JSContextRef, JSCWebWorker*> 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<std::mutex> lock(signalMutex);
|
||||
|
||||
terminateOnWorkerThread();
|
||||
terminationComplete = true;
|
||||
|
||||
signalCv.notify_one();
|
||||
});
|
||||
|
||||
std::unique_lock<std::mutex> 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <queue>
|
||||
|
||||
#include <JavaScriptCore/JSValueRef.h>
|
||||
|
||||
#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<MessageQueueThread> 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<void()>&& 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<MessageQueueThread> ownerMessageQueueThread_;
|
||||
std::unique_ptr<MessageQueueThread> workerMessageQueueThread_;
|
||||
JSGlobalContextRef context_ = nullptr;
|
||||
|
||||
static JSValueRef nativePostMessage(
|
||||
JSContextRef ctx,
|
||||
JSObjectRef function,
|
||||
JSObjectRef thisObject,
|
||||
size_t argumentCount,
|
||||
const JSValueRef arguments[],
|
||||
JSValueRef *exception);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
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<void()>&& runnable) {
|
||||
std::mutex signalMutex;
|
||||
std::condition_variable signalCv;
|
||||
bool runnableComplete = false;
|
||||
|
||||
runOnQueue([&] () mutable {
|
||||
std::lock_guard<std::mutex> lock(signalMutex);
|
||||
|
||||
runnable();
|
||||
runnableComplete = true;
|
||||
|
||||
signalCv.notify_one();
|
||||
});
|
||||
|
||||
std::unique_lock<std::mutex> lock(signalMutex);
|
||||
signalCv.wait(lock, [&runnableComplete] { return runnableComplete; });
|
||||
}
|
||||
};
|
||||
|
||||
}}
|
||||
|
|
Loading…
Reference in New Issue