mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-11 09:11:14 +08:00
WebWorkers: Move web worker impl to JSCExecutor
Summary: Part of the plan to make web workers able to call native modules. We will reuse the infrastructure already present in JSCExecutor to allow web workers to call native modules via the Bridge. Reviewed By: mhorowitz Differential Revision: D2926896 fb-gh-sync-id: 259b766c46f79bbb5df9d1c648237b81fc1cc1f9 shipit-source-id: 259b766c46f79bbb5df9d1c648237b81fc1cc1f9
This commit is contained in:
committed by
facebook-github-bot-5
parent
b454d31ab8
commit
febb1fbe13
@@ -27,6 +27,7 @@ import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
|
||||
import com.facebook.react.bridge.queue.QueueThreadExceptionHandler;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.common.futures.SimpleSettableFuture;
|
||||
import com.facebook.systrace.Systrace;
|
||||
import com.facebook.systrace.TraceListener;
|
||||
|
||||
@@ -264,6 +265,10 @@ public class CatalystInstanceImpl implements CatalystInstance {
|
||||
// TODO: tell all APIs to shut down
|
||||
mDestroyed = true;
|
||||
mJavaRegistry.notifyCatalystInstanceDestroy();
|
||||
|
||||
Systrace.unregisterListener(mTraceListener);
|
||||
|
||||
synchronouslyDisposeBridgeOnJSThread();
|
||||
mReactQueueConfiguration.destroy();
|
||||
boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
|
||||
if (!wasIdle && !mBridgeIdleListeners.isEmpty()) {
|
||||
@@ -271,12 +276,19 @@ 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);
|
||||
}
|
||||
});
|
||||
bridgeDisposeFuture.getOrThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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(T result) {
|
||||
public void set(@Nullable T result) {
|
||||
checkNotSet();
|
||||
mResult = result;
|
||||
mReadyLatch.countDown();
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
#include "JSCExecutor.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include <folly/json.h>
|
||||
#include <folly/String.h>
|
||||
#include <sys/time.h>
|
||||
@@ -88,6 +90,45 @@ JSCExecutor::JSCExecutor(Bridge *bridge, const std::string& cacheDir) :
|
||||
m_bridge(bridge),
|
||||
m_deviceCacheDir(cacheDir),
|
||||
m_messageQueueThread(MessageQueues::getCurrentMessageQueueThread()) {
|
||||
initOnJSVMThread();
|
||||
}
|
||||
|
||||
JSCExecutor::JSCExecutor(
|
||||
Bridge *bridge,
|
||||
int workerId,
|
||||
JSCExecutor *owner,
|
||||
const std::string& script) :
|
||||
m_bridge(bridge),
|
||||
m_workerId(workerId),
|
||||
m_owner(owner),
|
||||
m_deviceCacheDir(owner->m_deviceCacheDir),
|
||||
m_messageQueueThread(MessageQueues::getCurrentMessageQueueThread()) {
|
||||
// We post initOnJSVMThread here so that the owner doesn't have to wait for
|
||||
// initialization on its own thread
|
||||
m_messageQueueThread->runOnQueue([this, script] () {
|
||||
initOnJSVMThread();
|
||||
|
||||
installGlobalFunction(m_context, "postMessage", nativePostMessage);
|
||||
|
||||
// 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);
|
||||
@@ -114,18 +155,20 @@ JSCExecutor::JSCExecutor(Bridge *bridge, const std::string& cacheDir) :
|
||||
#endif
|
||||
}
|
||||
|
||||
JSCExecutor::~JSCExecutor() {
|
||||
// terminateWebWorker mutates m_webWorkers so collect all the workers to terminate first
|
||||
void JSCExecutor::terminateOnJSVMThread() {
|
||||
// terminateOwnedWebWorker mutates m_ownedWorkers so collect all the workers
|
||||
// to terminate first
|
||||
std::vector<int> workerIds;
|
||||
for (auto it = m_webWorkers.begin(); it != m_webWorkers.end(); it++) {
|
||||
workerIds.push_back(it->first);
|
||||
for (auto& it : m_ownedWorkers) {
|
||||
workerIds.push_back(it.first);
|
||||
}
|
||||
for (int workerId : workerIds) {
|
||||
terminateWebWorker(workerId);
|
||||
terminateOwnedWebWorker(workerId);
|
||||
}
|
||||
|
||||
s_globalContextRefToJSCExecutor.erase(m_context);
|
||||
JSGlobalContextRelease(m_context);
|
||||
m_context = nullptr;
|
||||
}
|
||||
|
||||
void JSCExecutor::loadApplicationScript(
|
||||
@@ -162,6 +205,10 @@ 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);
|
||||
@@ -253,56 +300,111 @@ void JSCExecutor::loadModule(uint32_t moduleId) {
|
||||
evaluateScript(m_context, source, sourceUrl);
|
||||
}
|
||||
|
||||
// WebWorker impl
|
||||
int JSCExecutor::addWebWorker(
|
||||
const std::string& script,
|
||||
JSValueRef workerRef) {
|
||||
static std::atomic_int nextWorkerId(1);
|
||||
int workerId = nextWorkerId++;
|
||||
|
||||
JSGlobalContextRef JSCExecutor::getContext() {
|
||||
return m_context;
|
||||
auto workerMQT = WebWorkerUtil::createWebWorkerThread(workerId, m_messageQueueThread.get());
|
||||
std::unique_ptr<JSCExecutor> worker;
|
||||
workerMQT->runOnQueueSync([this, &worker, &script, workerId] () {
|
||||
worker.reset(new JSCExecutor(m_bridge, workerId, this, script));
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::shared_ptr<MessageQueueThread> JSCExecutor::getMessageQueueThread() {
|
||||
return m_messageQueueThread;
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
void JSCExecutor::onMessageReceived(int workerId, const std::string& json) {
|
||||
Object& worker = m_webWorkerJSObjs.at(workerId);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
Value onmessageValue = worker.getProperty("onmessage");
|
||||
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");
|
||||
if (onmessageValue.isUndefined()) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSValueRef args[] = { JSCWebWorker::createMessageObject(m_context, json) };
|
||||
JSValueRef args[] = { createMessageObject(json) };
|
||||
onmessageValue.asObject().callAsFunction(1, args);
|
||||
|
||||
flush();
|
||||
}
|
||||
|
||||
int JSCExecutor::addWebWorker(const std::string& script, JSValueRef workerRef) {
|
||||
static std::atomic_int nextWorkerId(0);
|
||||
int workerId = nextWorkerId++;
|
||||
void JSCExecutor::receiveMessageFromOwner(const std::string& msgString) {
|
||||
CHECK(m_owner) << "Received message in a Executor that doesn't have an owner!";
|
||||
|
||||
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;
|
||||
JSValueRef args[] = { createMessageObject(msgString) };
|
||||
Value onmessageValue = Object::getGlobalObject(m_context).getProperty("onmessage");
|
||||
onmessageValue.asObject().callAsFunction(1, args);
|
||||
}
|
||||
|
||||
void JSCExecutor::postMessageToWebWorker(int workerId, JSValueRef message, JSValueRef *exn) {
|
||||
JSCWebWorker& worker = m_webWorkers.at(workerId);
|
||||
worker.postMessage(message);
|
||||
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::terminateWebWorker(int workerId) {
|
||||
JSCWebWorker& worker = m_webWorkers.at(workerId);
|
||||
|
||||
worker.terminate();
|
||||
|
||||
m_webWorkers.erase(workerId);
|
||||
m_webWorkerJSObjs.erase(workerId);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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,
|
||||
@@ -433,7 +535,7 @@ JSValueRef JSCExecutor::nativePostMessageToWorker(
|
||||
return JSValueMakeUndefined(ctx);
|
||||
}
|
||||
|
||||
executor->postMessageToWebWorker((int) workerDouble, arguments[1], exception);
|
||||
executor->postMessageToOwnedWebWorker((int) workerDouble, arguments[1], exception);
|
||||
|
||||
return JSValueMakeUndefined(ctx);
|
||||
}
|
||||
@@ -464,7 +566,7 @@ JSValueRef JSCExecutor::nativeTerminateWorker(
|
||||
return JSValueMakeUndefined(ctx);
|
||||
}
|
||||
|
||||
executor->terminateWebWorker((int) workerDouble);
|
||||
executor->terminateOwnedWebWorker((int) workerDouble);
|
||||
|
||||
return JSValueMakeUndefined(ctx);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <JavaScriptCore/JSContextRef.h>
|
||||
|
||||
#include "Executor.h"
|
||||
#include "JSCHelpers.h"
|
||||
#include "JSCWebWorker.h"
|
||||
#include "Value.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
@@ -23,10 +24,26 @@ private:
|
||||
std::string cacheDir_;
|
||||
};
|
||||
|
||||
class JSCExecutor : public JSExecutor, public JSCWebWorkerOwner {
|
||||
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 {
|
||||
public:
|
||||
/**
|
||||
* Should be invoked from the JS thread.
|
||||
* Must be invoked from thread this Executor will run on.
|
||||
*/
|
||||
explicit JSCExecutor(Bridge *bridge, const std::string& cacheDir);
|
||||
~JSCExecutor() override;
|
||||
@@ -56,25 +73,40 @@ 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;
|
||||
|
||||
int addWebWorker(const std::string& script, JSValueRef workerRef);
|
||||
void postMessageToWebWorker(int worker, JSValueRef message, JSValueRef *exn);
|
||||
/**
|
||||
* WebWorker constructor. Must be invoked from thread this Executor will run on.
|
||||
*/
|
||||
explicit JSCExecutor(
|
||||
Bridge *bridge,
|
||||
int workerId,
|
||||
JSCExecutor *owner,
|
||||
const std::string& script);
|
||||
|
||||
void initOnJSVMThread();
|
||||
void terminateOnJSVMThread();
|
||||
void flush();
|
||||
void terminateWebWorker(int worker);
|
||||
void loadModule(uint32_t moduleId);
|
||||
void flushQueueImmediate(std::string queueJSON);
|
||||
void loadModule(uint32_t moduleId);
|
||||
|
||||
int addWebWorker(const std::string& script, JSValueRef workerRef);
|
||||
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);
|
||||
|
||||
static JSValueRef nativeStartWorker(
|
||||
JSContextRef ctx,
|
||||
@@ -97,6 +129,13 @@ 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,
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// 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,7 +2,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
@@ -14,6 +16,24 @@ 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; });
|
||||
}
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user