Permalink
Browse files

WebWorkers: Add ExecutorToken to route native module calls to/from wo…

…rkers

Summary:To support native modules in web workers, native modules need to have an notion of the JS executor/thread that called into them in order to respond via Callback or JS module call to the right executor on the right thread.

ExecutorToken is an object that only serves as a token that can be used to identify an executor/message queue thread in the bridge. It doesn't expose any methods. Native modules Callback objects automatically have this ExecutionContext attached -- JSModule calls for modules that support workers will need to supply an appropriate ExecutorToken when retrieving the JSModule implementation from the ReactContext.

Reviewed By: mhorowitz

Differential Revision: D2965458

fb-gh-sync-id: 6e354d4df8536d40b12d02bd055f6d06b4ca595d
shipit-source-id: 6e354d4df8536d40b12d02bd055f6d06b4ca595d
  • Loading branch information...
astreet authored and Facebook Github Bot 4 committed Mar 1, 2016
1 parent f67fa82 commit bd95b22844e9facd2c6f34c644f4b33fd116c2d7
Showing with 614 additions and 116 deletions.
  1. +39 −20 ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java
  2. +4 −2 ReactAndroid/src/main/java/com/facebook/react/bridge/CallbackImpl.java
  3. +2 −2 ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java
  4. +21 −12 ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java
  5. +24 −0 ReactAndroid/src/main/java/com/facebook/react/bridge/ExecutorToken.java
  6. +43 −13 ReactAndroid/src/main/java/com/facebook/react/bridge/JavaScriptModuleRegistry.java
  7. +20 −2 ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java
  8. +4 −2 ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModuleRegistry.java
  9. +3 −2 ReactAndroid/src/main/java/com/facebook/react/bridge/ReactBridge.java
  10. +1 −1 ReactAndroid/src/main/java/com/facebook/react/bridge/ReactCallback.java
  11. +7 −0 ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java
  12. +2 −0 ReactAndroid/src/main/jni/react/BUCK
  13. +131 −19 ReactAndroid/src/main/jni/react/Bridge.cpp
  14. +62 −6 ReactAndroid/src/main/jni/react/Bridge.h
  15. +54 −0 ReactAndroid/src/main/jni/react/ExecutorToken.h
  16. +25 −0 ReactAndroid/src/main/jni/react/ExecutorTokenFactory.h
  17. +22 −9 ReactAndroid/src/main/jni/react/JSCExecutor.cpp
  18. +7 −9 ReactAndroid/src/main/jni/react/JSCExecutor.h
  19. +1 −0 ReactAndroid/src/main/jni/react/jni/Android.mk
  20. +4 −1 ReactAndroid/src/main/jni/react/jni/BUCK
  21. +20 −0 ReactAndroid/src/main/jni/react/jni/JExecutorToken.cpp
  22. +59 −0 ReactAndroid/src/main/jni/react/jni/JExecutorToken.h
  23. +24 −0 ReactAndroid/src/main/jni/react/jni/JExecutorTokenFactory.h
  24. +30 −11 ReactAndroid/src/main/jni/react/jni/OnLoad.cpp
  25. +2 −2 ReactAndroid/src/main/jni/react/jni/ProxyExecutor.cpp
  26. +3 −3 ReactAndroid/src/test/java/com/facebook/react/bridge/BaseJavaModuleTest.java
@@ -57,14 +57,14 @@ public int getJSArgumentsNeeded() {
}
public abstract @Nullable T extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex);
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex);
}
static final private ArgumentExtractor<Boolean> ARGUMENT_EXTRACTOR_BOOLEAN =
new ArgumentExtractor<Boolean>() {
@Override
public Boolean extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return jsArguments.getBoolean(atIndex);
}
};
@@ -73,7 +73,7 @@ public Boolean extractArgument(
new ArgumentExtractor<Double>() {
@Override
public Double extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return jsArguments.getDouble(atIndex);
}
};
@@ -82,7 +82,7 @@ public Double extractArgument(
new ArgumentExtractor<Float>() {
@Override
public Float extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return (float) jsArguments.getDouble(atIndex);
}
};
@@ -91,7 +91,7 @@ public Float extractArgument(
new ArgumentExtractor<Integer>() {
@Override
public Integer extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return (int) jsArguments.getDouble(atIndex);
}
};
@@ -100,7 +100,7 @@ public Integer extractArgument(
new ArgumentExtractor<String>() {
@Override
public String extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return jsArguments.getString(atIndex);
}
};
@@ -109,7 +109,7 @@ public String extractArgument(
new ArgumentExtractor<ReadableNativeArray>() {
@Override
public ReadableNativeArray extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return jsArguments.getArray(atIndex);
}
};
@@ -118,7 +118,7 @@ public ReadableNativeArray extractArgument(
new ArgumentExtractor<ReadableMap>() {
@Override
public ReadableMap extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
return jsArguments.getMap(atIndex);
}
};
@@ -127,12 +127,12 @@ public ReadableMap extractArgument(
new ArgumentExtractor<Callback>() {
@Override
public @Nullable Callback extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
if (jsArguments.isNull(atIndex)) {
return null;
} else {
int id = (int) jsArguments.getDouble(atIndex);
return new CallbackImpl(catalystInstance, id);
return new CallbackImpl(catalystInstance, executorToken, id);
}
}
};
@@ -146,11 +146,11 @@ public int getJSArgumentsNeeded() {
@Override
public Promise extractArgument(
CatalystInstance catalystInstance, ReadableNativeArray jsArguments, int atIndex) {
CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex) {
Callback resolve = ARGUMENT_EXTRACTOR_CALLBACK
.extractArgument(catalystInstance, jsArguments, atIndex);
.extractArgument(catalystInstance, executorToken, jsArguments, atIndex);
Callback reject = ARGUMENT_EXTRACTOR_CALLBACK
.extractArgument(catalystInstance, jsArguments, atIndex + 1);
.extractArgument(catalystInstance, executorToken, jsArguments, atIndex + 1);
return new PromiseImpl(resolve, reject);
}
};
@@ -174,9 +174,21 @@ public JavaMethod(Method method) {
}
private ArgumentExtractor[] buildArgumentExtractors(Class[] paramTypes) {
ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[paramTypes.length];
for (int i = 0; i < paramTypes.length; i += argumentExtractors[i].getJSArgumentsNeeded()) {
Class argumentClass = paramTypes[i];
// Modules that support web workers are expected to take an ExecutorToken as the first
// parameter to all their @ReactMethod-annotated methods. We compensate for that here.
int executorTokenOffset = 0;
if (BaseJavaModule.this.supportsWebWorkers()) {
if (paramTypes[0] != ExecutorToken.class) {
throw new RuntimeException(
"Module " + BaseJavaModule.this + " supports web workers, but " + mMethod.getName() +
"does not take an ExecutorToken as its first parameter.");
}
executorTokenOffset = 1;
}
ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[paramTypes.length - executorTokenOffset];
for (int i = 0; i < paramTypes.length - executorTokenOffset; i += argumentExtractors[i].getJSArgumentsNeeded()) {
Class argumentClass = paramTypes[i + executorTokenOffset];
if (argumentClass == Boolean.class || argumentClass == boolean.class) {
argumentExtractors[i] = ARGUMENT_EXTRACTOR_BOOLEAN;
} else if (argumentClass == Integer.class || argumentClass == int.class) {
@@ -220,7 +232,7 @@ private String getAffectedRange(int startIndex, int jsArgumentsNeeded) {
}
@Override
public void invoke(CatalystInstance catalystInstance, ReadableNativeArray parameters) {
public void invoke(CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray parameters) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "callJavaModuleMethod");
try {
if (mJSArgumentsNeeded != parameters.size()) {
@@ -229,11 +241,18 @@ public void invoke(CatalystInstance catalystInstance, ReadableNativeArray parame
parameters.size() + " arguments, expected " + mJSArgumentsNeeded);
}
// Modules that support web workers are expected to take an ExecutorToken as the first
// parameter to all their @ReactMethod-annotated methods. We compensate for that here.
int i = 0, jsArgumentsConsumed = 0;
int executorTokenOffset = 0;
if (BaseJavaModule.this.supportsWebWorkers()) {
mArguments[0] = executorToken;
executorTokenOffset = 1;
}
try {
for (; i < mArgumentExtractors.length; i++) {
mArguments[i] = mArgumentExtractors[i].extractArgument(
catalystInstance, parameters, jsArgumentsConsumed);
mArguments[i + executorTokenOffset] = mArgumentExtractors[i].extractArgument(
catalystInstance, executorToken, parameters, jsArgumentsConsumed);
jsArgumentsConsumed += mArgumentExtractors[i].getJSArgumentsNeeded();
}
} catch (UnexpectedNativeTypeException e) {
@@ -341,7 +360,7 @@ public void onReactBridgeInitialized(ReactBridge bridge) {
public void onCatalystInstanceDestroy() {
// do nothing
}
@Override
public boolean supportsWebWorkers() {
return false;
@@ -15,15 +15,17 @@
public final class CallbackImpl implements Callback {
private final CatalystInstance mCatalystInstance;
private final ExecutorToken mExecutorToken;
private final int mCallbackId;
public CallbackImpl(CatalystInstance bridge, int callbackId) {
public CallbackImpl(CatalystInstance bridge, ExecutorToken executorToken, int callbackId) {
mCatalystInstance = bridge;
mExecutorToken = executorToken;
mCallbackId = callbackId;
}
@Override
public void invoke(Object... args) {
mCatalystInstance.invokeCallback(mCallbackId, Arguments.fromJavaArgs(args));
mCatalystInstance.invokeCallback(mExecutorToken, mCallbackId, Arguments.fromJavaArgs(args));
}
}
@@ -14,7 +14,6 @@
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.common.annotations.VisibleForTesting;
/**
* A higher level API on top of the asynchronous JSC bridge. This provides an
@@ -27,7 +26,7 @@
// This is called from java code, so it won't be stripped anyway, but proguard will rename it,
// which this prevents.
@DoNotStrip
void invokeCallback(final int callbackID, final NativeArray arguments);
void invokeCallback(ExecutorToken executorToken, final int callbackID, final NativeArray arguments);
/**
* Destroys this catalyst instance, waiting for any other threads in ReactQueueConfiguration
* (besides the UI thread) to finish running. Must be called from the UI thread so that we can
@@ -45,6 +44,7 @@
ReactQueueConfiguration getReactQueueConfiguration();
<T extends JavaScriptModule> T getJSModule(Class<T> jsInterface);
<T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface);
<T extends NativeModule> T getNativeModule(Class<T> nativeModuleInterface);
Collection<NativeModule> getNativeModules();
@@ -54,6 +54,7 @@
private final JavaScriptModuleRegistry mJSModuleRegistry;
private final JSBundleLoader mJSBundleLoader;
private final Object mTeardownLock = new Object();
private @Nullable ExecutorToken mMainExecutorToken;
// Access from native modules thread
private final NativeModuleRegistry mJavaRegistry;
@@ -113,6 +114,7 @@ private ReactBridge initializeBridge(
jsExecutor,
new NativeModulesReactCallback(),
mReactQueueConfiguration.getNativeModulesQueueThread());
mMainExecutorToken = bridge.getMainExecutorToken();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
@@ -165,10 +167,11 @@ public Boolean call() throws Exception {
}
/* package */ void callFunction(
final int moduleId,
final int methodId,
final NativeArray arguments,
final String tracingName) {
ExecutorToken executorToken,
int moduleId,
int methodId,
NativeArray arguments,
String tracingName) {
synchronized (mTeardownLock) {
if (mDestroyed) {
FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
@@ -177,15 +180,15 @@ public Boolean call() throws Exception {
incrementPendingJSCalls();
Assertions.assertNotNull(mBridge).callFunction(moduleId, methodId, arguments, tracingName);
Assertions.assertNotNull(mBridge).callFunction(executorToken, moduleId, methodId, arguments, tracingName);
}
}
// This is called from java code, so it won't be stripped anyway, but proguard will rename it,
// which this prevents.
@DoNotStrip
@Override
public void invokeCallback(final int callbackID, final NativeArray arguments) {
public void invokeCallback(ExecutorToken executorToken, int callbackID, NativeArray arguments) {
synchronized (mTeardownLock) {
if (mDestroyed) {
FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed.");
@@ -194,7 +197,7 @@ public void invokeCallback(final int callbackID, final NativeArray arguments) {
incrementPendingJSCalls();
Assertions.assertNotNull(mBridge).invokeCallback(callbackID, arguments);
Assertions.assertNotNull(mBridge).invokeCallback(executorToken, callbackID, arguments);
}
}
@@ -269,7 +272,12 @@ public ReactQueueConfiguration getReactQueueConfiguration() {
@Override
public <T extends JavaScriptModule> T getJSModule(Class<T> jsInterface) {
return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(jsInterface);
return getJSModule(Assertions.assertNotNull(mMainExecutorToken), jsInterface);
}
@Override
public <T extends JavaScriptModule> T getJSModule(ExecutorToken executorToken, Class<T> jsInterface) {
return Assertions.assertNotNull(mJSModuleRegistry).getJavaScriptModule(executorToken, jsInterface);
}
@Override
@@ -388,15 +396,15 @@ private void decrementPendingJSCalls() {
private class NativeModulesReactCallback implements ReactCallback {
@Override
public void call(int moduleId, int methodId, ReadableNativeArray parameters) {
public void call(ExecutorToken executorToken, int moduleId, int methodId, ReadableNativeArray parameters) {
mReactQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
// Suppress any callbacks if destroyed - will only lead to sadness.
if (mDestroyed) {
return;
}
mJavaRegistry.call(CatalystInstanceImpl.this, moduleId, methodId, parameters);
mJavaRegistry.call(CatalystInstanceImpl.this, executorToken, moduleId, methodId, parameters);
}
@Override
@@ -441,12 +449,13 @@ public void run() {
private class JSProfilerTraceListener implements TraceListener {
@Override
public void onTraceStarted() {
getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(true);
getJSModule(Assertions.assertNotNull(mMainExecutorToken), com.facebook.react.bridge.Systrace.class).setEnabled(
true);
}
@Override
public void onTraceStopped() {
getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(false);
getJSModule(Assertions.assertNotNull(mMainExecutorToken), com.facebook.react.bridge.Systrace.class).setEnabled(false);
}
}
@@ -0,0 +1,24 @@
package com.facebook.react.bridge;
import com.facebook.jni.HybridData;
import com.facebook.proguard.annotations.DoNotStrip;
/**
* Class corresponding to a JS VM that can call into native modules. In Java, this should
* just be treated as a black box to be used to help the framework route native->JS calls back to
* the proper JS VM. See {@link ReactContext#getJSModule(ExecutorToken, Class)} and
* {@link BaseJavaModule#supportsWebWorkers()}.
*
* Note: If your application doesn't use web workers, it will only have a single ExecutorToken
* per instance of React Native.
*/
@DoNotStrip
public class ExecutorToken {
private final HybridData mHybridData;
@DoNotStrip
private ExecutorToken(HybridData hybridData) {
mHybridData = hybridData;
}
}
Oops, something went wrong.

0 comments on commit bd95b22

Please sign in to comment.