Permalink
Browse files

WebWorkers: Implement initial WebWorkers API

Summary:
public

Implements a basic WebWorkers API that allows posting messages between the main JS thread and a worker background thread. It follows the existing webworkers API from JS. Currently passed memory needs to be JSON serializable and is copied (unfortunately, this is what webkit does as well, but with a more advanced serialization/deserialization process).

There are a lot of TODO's: I'll add tasks for them once this is accepted.

Reviewed By: lexs

Differential Revision: D2779349

fb-gh-sync-id: 8ed04c115d36acf0264ef1f6a12a65dd0c14ff18
  • Loading branch information...
astreet authored and facebook-github-bot-4 committed Jan 12, 2016
1 parent dd60964 commit 72d1826ae3a8847ba1e4fd33621a02d495258496
@@ -402,7 +402,8 @@ private void incrementPendingJSCalls() {
private void decrementPendingJSCalls() {
int newPendingCalls = mPendingJSCalls.decrementAndGet();
- Assertions.assertCondition(newPendingCalls >= 0);
+ // TODO(9604406): handle case of web workers injecting messages to main thread
+ //Assertions.assertCondition(newPendingCalls >= 0);
boolean isNowIdle = newPendingCalls == 0;
Systrace.traceCounter(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.bridge.queue;
+
+/**
+ * An Exception handler that posts the Exception to be thrown on the given delegate
+ * MessageQueueThread.
+ */
+public class ProxyQueueThreadExceptionHandler implements QueueThreadExceptionHandler {
+
+ private final MessageQueueThread mDelegateThread;
+
+ public ProxyQueueThreadExceptionHandler(MessageQueueThread delegateThread) {
+ mDelegateThread = delegateThread;
+ }
+
+ @Override
+ public void handleException(final Exception e) {
+ mDelegateThread.runOnQueue(
+ new Runnable() {
+ @Override
+ public void run() {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+}
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.bridge.webworkers;
+
+import com.facebook.proguard.annotations.DoNotStrip;
+import com.facebook.react.bridge.queue.MessageQueueThread;
+import com.facebook.react.bridge.queue.MessageQueueThreadImpl;
+import com.facebook.react.bridge.queue.ProxyQueueThreadExceptionHandler;
+
+@DoNotStrip
+public class WebWorkers {
+
+ /**
+ * Creates a new MessageQueueThread for a background web worker owned by the JS thread with the
+ * given MessageQueueThread.
+ */
+ public static MessageQueueThread createWebWorkerThread(int id, MessageQueueThread ownerThread) {
+ return MessageQueueThreadImpl.startNewBackgroundThread(
+ "web-worker-" + id,
+ new ProxyQueueThreadExceptionHandler(ownerThread));
+ }
+}
@@ -18,7 +18,7 @@ class JSThreadState {
JSThreadState(const RefPtr<JSExecutorFactory>& jsExecutorFactory, Bridge::Callback&& callback) :
m_callback(callback)
{
- m_jsExecutor = jsExecutorFactory->createJSExecutor([this, callback] (std::string queueJSON) {
+ m_jsExecutor = jsExecutorFactory->createJSExecutor([this, callback] (std::string queueJSON, bool isEndOfBatch) {
m_callback(parseMethodCalls(queueJSON), false /* = isEndOfBatch */);
});
}
@@ -18,7 +18,7 @@ namespace react {
class JSExecutor;
-typedef std::function<void(std::string)> FlushImmediateCallback;
+typedef std::function<void(std::string, bool)> FlushImmediateCallback;
class JSExecutorFactory : public Countable {
public:
@@ -3,6 +3,7 @@
#include "JSCExecutor.h"
#include <algorithm>
+#include <atomic>
#include <sstream>
#include <string>
#include <fb/log.h>
@@ -11,6 +12,7 @@
#include <jni/fbjni/Exceptions.h>
#include <sys/time.h>
#include "Value.h"
+#include "jni/JMessageQueueThread.h"
#include "jni/OnLoad.h"
#ifdef WITH_JSC_EXTRA_TRACING
@@ -48,6 +50,7 @@ namespace facebook {
namespace react {
static std::unordered_map<JSContextRef, JSCExecutor*> s_globalContextRefToJSCExecutor;
+
static JSValueRef nativeFlushQueueImmediate(
JSContextRef ctx,
JSObjectRef function,
@@ -96,10 +99,13 @@ std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(FlushImmediateC
JSCExecutor::JSCExecutor(FlushImmediateCallback cb) :
m_flushImmediateCallback(cb) {
m_context = JSGlobalContextCreateInGroup(nullptr, nullptr);
+ m_messageQueueThread = JMessageQueueThread::currentMessageQueueThread();
s_globalContextRefToJSCExecutor[m_context] = this;
installGlobalFunction(m_context, "nativeFlushQueueImmediate", nativeFlushQueueImmediate);
installGlobalFunction(m_context, "nativeLoggingHook", nativeLoggingHook);
installGlobalFunction(m_context, "nativePerformanceNow", nativePerformanceNow);
+ installGlobalFunction(m_context, "nativeStartWorker", nativeStartWorker);
+ installGlobalFunction(m_context, "nativePostMessageToWorker", nativePostMessageToWorker);
#ifdef WITH_FB_JSC_TUNING
configureJSCForAndroid();
@@ -219,13 +225,55 @@ void JSCExecutor::handleMemoryPressureCritical() {
}
void JSCExecutor::flushQueueImmediate(std::string queueJSON) {
- m_flushImmediateCallback(queueJSON);
+ m_flushImmediateCallback(queueJSON, false);
+}
+
+// WebWorker impl
+
+JSGlobalContextRef JSCExecutor::getContext() {
+ return m_context;
+}
+
+std::shared_ptr<JMessageQueueThread> JSCExecutor::getMessageQueueThread() {
+ return m_messageQueueThread;
+}
+
+void JSCExecutor::onMessageReceived(int workerId, const std::string& json) {
+ Value rebornJSMsg = Value::fromJSON(m_context, String(json.c_str()));
+ JSValueRef args[] = { rebornJSMsg };
+ Object& worker = m_webWorkerJSObjs.at(workerId);
+
+ Value onmessageValue = worker.getProperty("onmessage");
+ if (onmessageValue.isUndefined()) {
+ return;
+ }
+ onmessageValue.asObject().callAsFunction(1, args);
+
+ m_flushImmediateCallback(flush(), true);
+}
+
+int JSCExecutor::addWebWorker(const std::string& script, JSValueRef workerRef) {
+ static std::atomic_int nextWorkerId(0);
+ int workerId = nextWorkerId++;
+
+ 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::postMessageToWebWorker(int workerId, JSValueRef message, JSValueRef *exn) {
+ JSCWebWorker& worker = m_webWorkers.at(workerId);
+ worker.postMessage(message);
}
static JSValueRef createErrorString(JSContextRef ctx, const char *msg) {
return JSValueMakeString(ctx, String(msg));
}
+// Native JS hooks
+
static JSValueRef nativeFlushQueueImmediate(
JSContextRef ctx,
JSObjectRef function,
@@ -253,6 +301,66 @@ static JSValueRef nativeFlushQueueImmediate(
return JSValueMakeUndefined(ctx);
}
+JSValueRef JSCExecutor::nativeStartWorker(
+ JSContextRef ctx,
+ JSObjectRef function,
+ JSObjectRef thisObject,
+ size_t argumentCount,
+ const JSValueRef arguments[],
+ JSValueRef *exception) {
+ if (argumentCount != 2) {
+ *exception = createErrorString(ctx, "Got wrong number of args");
+ return JSValueMakeUndefined(ctx);
+ }
+
+ std::string scriptFile = Value(ctx, arguments[0]).toString().str();
+
+ JSValueRef worker = arguments[1];
+
+ JSCExecutor *executor;
+ try {
+ executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
+ } catch (std::out_of_range& e) {
+ *exception = createErrorString(ctx, "Global JS context didn't map to a valid executor");
+ return JSValueMakeUndefined(ctx);
+ }
+
+ int workerId = executor->addWebWorker(scriptFile, worker);
+
+ return JSValueMakeNumber(ctx, workerId);
+}
+
+JSValueRef JSCExecutor::nativePostMessageToWorker(
+ JSContextRef ctx,
+ JSObjectRef function,
+ JSObjectRef thisObject,
+ size_t argumentCount,
+ const JSValueRef arguments[],
+ JSValueRef *exception) {
+ if (argumentCount != 2) {
+ *exception = createErrorString(ctx, "Got wrong number of args");
+ return JSValueMakeUndefined(ctx);
+ }
+
+ double workerDouble = JSValueToNumber(ctx, arguments[0], exception);
+ if (workerDouble != workerDouble) {
+ *exception = createErrorString(ctx, "Got invalid worker id");
+ return JSValueMakeUndefined(ctx);
+ }
+
+ JSCExecutor *executor;
+ try {
+ executor = s_globalContextRefToJSCExecutor.at(JSContextGetGlobalContext(ctx));
+ } catch (std::out_of_range& e) {
+ *exception = createErrorString(ctx, "Global JS context didn't map to a valid executor");
+ return JSValueMakeUndefined(ctx);
+ }
+
+ executor->postMessageToWebWorker((int) workerDouble, arguments[1], exception);
+
+ return JSValueMakeUndefined(ctx);
+}
+
static JSValueRef nativeLoggingHook(
JSContextRef ctx,
JSObjectRef function,
@@ -2,20 +2,28 @@
#pragma once
+#include <memory>
+#include <unordered_map>
#include <JavaScriptCore/JSContextRef.h>
#include "Executor.h"
#include "JSCHelpers.h"
+#include "JSCWebWorker.h"
namespace facebook {
namespace react {
+class JMessageQueueThread;
+
class JSCExecutorFactory : public JSExecutorFactory {
public:
virtual std::unique_ptr<JSExecutor> createJSExecutor(FlushImmediateCallback cb) override;
};
-class JSCExecutor : public JSExecutor {
+class JSCExecutor : public JSExecutor, public JSCWebWorkerOwner {
public:
+ /**
+ * Should be invoked from the JS thread.
+ */
explicit JSCExecutor(FlushImmediateCallback flushImmediateCallback);
~JSCExecutor() override;
@@ -41,10 +49,34 @@ class JSCExecutor : public JSExecutor {
void flushQueueImmediate(std::string queueJSON);
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<JMessageQueueThread> getMessageQueueThread() override;
private:
JSGlobalContextRef m_context;
FlushImmediateCallback m_flushImmediateCallback;
+ std::unordered_map<int, JSCWebWorker> m_webWorkers;
+ std::unordered_map<int, Object> m_webWorkerJSObjs;
+ std::shared_ptr<JMessageQueueThread> m_messageQueueThread;
+
+ int addWebWorker(const std::string& script, JSValueRef workerRef);
+ void postMessageToWebWorker(int worker, JSValueRef message, JSValueRef *exn);
+
+ static JSValueRef nativeStartWorker(
+ JSContextRef ctx,
+ JSObjectRef function,
+ JSObjectRef thisObject,
+ size_t argumentCount,
+ const JSValueRef arguments[],
+ JSValueRef *exception);
+ static JSValueRef nativePostMessageToWorker(
+ JSContextRef ctx,
+ JSObjectRef function,
+ JSObjectRef thisObject,
+ size_t argumentCount,
+ const JSValueRef arguments[],
+ JSValueRef *exception);
};
} }
Oops, something went wrong.

0 comments on commit 72d1826

Please sign in to comment.