WebWorkers: Allow native modules to be notified when executors are unregistered

Summary: This will allow them to clean up resources when a web worker goes away.

Reviewed By: mhorowitz, lexs

Differential Revision: D2994721

fb-gh-sync-id: c7ca1afc7290e85038cf692a139f6478dba0ef61
shipit-source-id: c7ca1afc7290e85038cf692a139f6478dba0ef61
This commit is contained in:
Andy Street 2016-03-03 04:09:27 -08:00 committed by Facebook Github Bot 3
parent 1bab7c5182
commit 6d5f9ddfff
7 changed files with 157 additions and 58 deletions

View File

@ -222,8 +222,10 @@ public class CatalystInstanceImpl implements CatalystInstance {
Systrace.unregisterListener(mTraceListener);
synchronouslyDisposeBridgeOnJSThread();
mReactQueueConfiguration.destroy();
}
mReactQueueConfiguration.destroy();
boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
if (!wasIdle && !mBridgeIdleListeners.isEmpty()) {
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
@ -293,12 +295,12 @@ public class CatalystInstanceImpl implements CatalystInstance {
@Override
public void handleMemoryPressure(final MemoryPressure level) {
mReactQueueConfiguration.getJSQueueThread().runOnQueue(
new Runnable() {
@Override
public void run() {
Assertions.assertNotNull(mBridge).handleMemoryPressure(level);
}
});
new Runnable() {
@Override
public void run() {
Assertions.assertNotNull(mBridge).handleMemoryPressure(level);
}
});
}
/**
@ -399,12 +401,14 @@ public class CatalystInstanceImpl implements CatalystInstance {
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;
}
synchronized (mTeardownLock) {
// Suppress any callbacks if destroyed - will only lead to sadness.
if (mDestroyed) {
return;
}
mJavaRegistry.call(CatalystInstanceImpl.this, executorToken, moduleId, methodId, parameters);
mJavaRegistry.call(CatalystInstanceImpl.this, executorToken, moduleId, methodId, parameters);
}
}
@Override
@ -415,17 +419,38 @@ public class CatalystInstanceImpl implements CatalystInstance {
// native modules could be in a bad state so we don't want to call anything on them. We
// still want to trigger the debug listener since it allows instrumentation tests to end and
// check their assertions without waiting for a timeout.
if (!mDestroyed) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchComplete");
try {
mJavaRegistry.onBatchComplete();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
synchronized (mTeardownLock) {
if (!mDestroyed) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchComplete");
try {
mJavaRegistry.onBatchComplete();
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
}
decrementPendingJSCalls();
}
@Override
public void onExecutorUnregistered(ExecutorToken executorToken) {
mReactQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
// Since onCatalystInstanceDestroy happens on the UI thread, we don't want to also execute
// this callback on the native modules thread at the same time. Longer term, onCatalystInstanceDestroy
// should probably be executed on the native modules thread as well instead.
synchronized (mTeardownLock) {
if (!mDestroyed) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onExecutorUnregistered");
try {
mJavaRegistry.onExecutorUnregistered(executorToken);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
}
}
}
private class NativeExceptionHandler implements QueueThreadExceptionHandler {

View File

@ -30,6 +30,7 @@ public class NativeModuleRegistry {
private final List<ModuleDefinition> mModuleTable;
private final Map<Class<? extends NativeModule>, NativeModule> mModuleInstances;
private final ArrayList<OnBatchCompleteListener> mBatchCompleteListenerModules;
private final ArrayList<OnExecutorUnregisteredListener> mOnExecutorUnregisteredListenerModules;
private NativeModuleRegistry(
List<ModuleDefinition> moduleTable,
@ -37,12 +38,16 @@ public class NativeModuleRegistry {
mModuleTable = moduleTable;
mModuleInstances = moduleInstances;
mBatchCompleteListenerModules = new ArrayList<OnBatchCompleteListener>(mModuleTable.size());
mBatchCompleteListenerModules = new ArrayList<>(mModuleTable.size());
mOnExecutorUnregisteredListenerModules = new ArrayList<>(mModuleTable.size());
for (int i = 0; i < mModuleTable.size(); i++) {
ModuleDefinition definition = mModuleTable.get(i);
if (definition.target instanceof OnBatchCompleteListener) {
mBatchCompleteListenerModules.add((OnBatchCompleteListener) definition.target);
}
if (definition.target instanceof OnExecutorUnregisteredListener) {
mOnExecutorUnregisteredListenerModules.add((OnExecutorUnregisteredListener) definition.target);
}
}
}
@ -135,6 +140,12 @@ public class NativeModuleRegistry {
}
}
public void onExecutorUnregistered(ExecutorToken executorToken) {
for (int i = 0; i < mOnExecutorUnregisteredListenerModules.size(); i++) {
mOnExecutorUnregisteredListenerModules.get(i).onExecutorDestroyed(executorToken);
}
}
public <T extends NativeModule> T getModule(Class<T> moduleInterface) {
return (T) Assertions.assertNotNull(mModuleInstances.get(moduleInterface));
}

View File

@ -0,0 +1,22 @@
/**
* 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;
/**
* Interface for a module that will be notified when JS executors have been unregistered from the bridge.
* Note that this will NOT notify listeners about the main executor being destroyed: use
* {@link NativeModule#onCatalystInstanceDestroy()} for that. Once a module has received a
* {@link NativeModule#onCatalystInstanceDestroy()} call, it will not receive any onExecutorUnregistered
* calls.
*/
public interface OnExecutorUnregisteredListener {
void onExecutorDestroyed(ExecutorToken executorToken);
}

View File

@ -19,4 +19,7 @@ public interface ReactCallback {
@DoNotStrip
void onBatchComplete();
@DoNotStrip
void onExecutorUnregistered(ExecutorToken executorToken);
}

View File

@ -17,7 +17,7 @@ namespace react {
Bridge::Bridge(
JSExecutorFactory* jsExecutorFactory,
std::unique_ptr<ExecutorTokenFactory> executorTokenFactory,
Callback callback) :
std::unique_ptr<BridgeCallback> callback) :
m_callback(std::move(callback)),
m_destroyed(std::make_shared<bool>(false)),
m_executorTokenFactory(std::move(executorTokenFactory)) {
@ -174,7 +174,7 @@ void Bridge::callNativeModules(JSExecutor& executor, const std::string& callJSON
if (*m_destroyed) {
return;
}
m_callback(getTokenForExecutor(executor), parseMethodCalls(callJSON), isEndOfBatch);
m_callback->onCallNativeModules(getTokenForExecutor(executor), parseMethodCalls(callJSON), isEndOfBatch);
}
ExecutorToken Bridge::getMainExecutorToken() const {
@ -214,7 +214,7 @@ std::unique_ptr<JSExecutor> Bridge::unregisterExecutor(ExecutorToken executorTok
m_executorTokenMap.erase(executor.get());
}
// TODO: Notify native modules that ExecutorToken destroyed
m_callback->onExecutorUnregistered(executorToken);
return executor;
}

View File

@ -24,6 +24,18 @@ struct dynamic;
namespace facebook {
namespace react {
class BridgeCallback {
public:
virtual ~BridgeCallback() {};
virtual void onCallNativeModules(
ExecutorToken executorToken,
std::vector<MethodCall>&& calls,
bool isEndOfBatch) = 0;
virtual void onExecutorUnregistered(ExecutorToken executorToken) = 0;
};
class Bridge;
class ExecutorRegistration {
public:
@ -39,15 +51,13 @@ public:
class Bridge {
public:
typedef std::function<void(ExecutorToken executorToken, std::vector<MethodCall>, bool isEndOfBatch)> Callback;
/**
* This must be called on the main JS thread.
*/
Bridge(
JSExecutorFactory* jsExecutorFactory,
std::unique_ptr<ExecutorTokenFactory> executorTokenFactory,
Callback callback);
std::unique_ptr<BridgeCallback> callback);
virtual ~Bridge();
/**
@ -128,7 +138,7 @@ public:
*/
void destroy();
private:
Callback m_callback;
std::unique_ptr<BridgeCallback> m_callback;
// This is used to avoid a race condition where a proxyCallback gets queued after ~Bridge(),
// on the same thread. In that case, the callback will try to run the task on m_callback which
// will have been destroyed within ~Bridge(), thus causing a SIGSEGV.

View File

@ -567,6 +567,7 @@ namespace bridge {
static jmethodID gCallbackMethod;
static jmethodID gOnBatchCompleteMethod;
static jmethodID gOnExecutorUnregisteredMethod;
static jmethodID gLogMarkerMethod;
struct CountableBridge : Bridge, Countable {
@ -582,7 +583,7 @@ static void logMarker(const std::string& marker) {
env->DeleteLocalRef(jmarker);
}
static void makeJavaCall(JNIEnv* env, ExecutorToken executorToken, jobject callback, MethodCall&& call) {
static void makeJavaCall(JNIEnv* env, ExecutorToken executorToken, jobject callback, const MethodCall& call) {
if (call.arguments.isNull()) {
return;
}
@ -607,33 +608,51 @@ static void signalBatchComplete(JNIEnv* env, jobject callback) {
env->CallVoidMethod(callback, gOnBatchCompleteMethod);
}
static void dispatchCallbacksToJava(ExecutorToken executorToken,
const RefPtr<WeakReference>& weakCallback,
const RefPtr<WeakReference>& weakCallbackQueueThread,
std::vector<MethodCall>&& calls,
bool isEndOfBatch) {
auto env = Environment::current();
if (env->ExceptionCheck()) {
FBLOGW("Dropped calls because of pending exception");
return;
}
class PlatformBridgeCallback : public BridgeCallback {
public:
PlatformBridgeCallback(
RefPtr<WeakReference> weakCallback_,
RefPtr<WeakReference> weakCallbackQueueThread_) :
weakCallback_(std::move(weakCallback_)),
weakCallbackQueueThread_(std::move(weakCallbackQueueThread_)) {}
ResolvedWeakReference callbackQueueThread(weakCallbackQueueThread);
if (!callbackQueueThread) {
FBLOGW("Dropped calls because of callback queue thread went away");
return;
}
auto runnableFunction = std::bind([executorToken, weakCallback, isEndOfBatch] (std::vector<MethodCall>& calls) {
void executeCallbackOnCallbackQueueThread(std::function<void(ResolvedWeakReference&)>&& runnable) {
auto env = Environment::current();
if (env->ExceptionCheck()) {
FBLOGW("Dropped calls because of pending exception");
FBLOGW("Dropped callback because of pending exception");
return;
}
ResolvedWeakReference callback(weakCallback);
if (callback) {
for (auto&& call : calls) {
makeJavaCall(env, executorToken, callback, std::move(call));
ResolvedWeakReference callbackQueueThread(weakCallbackQueueThread_);
if (!callbackQueueThread) {
FBLOGW("Dropped callback because callback queue thread went away");
return;
}
auto runnableWrapper = std::bind([this] (std::function<void(ResolvedWeakReference&)>& runnable) {
auto env = Environment::current();
if (env->ExceptionCheck()) {
FBLOGW("Dropped calls because of pending exception");
return;
}
ResolvedWeakReference callback(weakCallback_);
if (callback) {
runnable(callback);
}
}, std::move(runnable));
auto jNativeRunnable = runnable::createNativeRunnable(env, std::move(runnableWrapper));
queue::enqueueNativeRunnableOnQueue(env, callbackQueueThread, jNativeRunnable.get());
}
virtual void onCallNativeModules(
ExecutorToken executorToken,
std::vector<MethodCall>&& calls,
bool isEndOfBatch) override {
executeCallbackOnCallbackQueueThread([executorToken, calls, isEndOfBatch] (ResolvedWeakReference& callback) {
JNIEnv* env = Environment::current();
for (auto& call : calls) {
makeJavaCall(env, executorToken, callback, call);
if (env->ExceptionCheck()) {
return;
}
@ -641,23 +660,31 @@ static void dispatchCallbacksToJava(ExecutorToken executorToken,
if (isEndOfBatch) {
signalBatchComplete(env, callback);
}
}
}, std::move(calls));
});
}
auto jNativeRunnable = runnable::createNativeRunnable(env, std::move(runnableFunction));
queue::enqueueNativeRunnableOnQueue(env, callbackQueueThread, jNativeRunnable.get());
}
virtual void onExecutorUnregistered(ExecutorToken executorToken) override {
executeCallbackOnCallbackQueueThread([executorToken] (ResolvedWeakReference& callback) {
JNIEnv *env = Environment::current();
env->CallVoidMethod(
callback,
gOnExecutorUnregisteredMethod,
static_cast<JExecutorTokenHolder*>(executorToken.getPlatformExecutorToken().get())->getJobj());
});
}
private:
RefPtr<WeakReference> weakCallback_;
RefPtr<WeakReference> weakCallbackQueueThread_;
};
static void create(JNIEnv* env, jobject obj, jobject executor, jobject callback,
jobject callbackQueueThread) {
auto weakCallback = createNew<WeakReference>(callback);
auto weakCallbackQueueThread = createNew<WeakReference>(callbackQueueThread);
auto bridgeCallback = [weakCallback, weakCallbackQueueThread] (ExecutorToken executorToken, std::vector<MethodCall> calls, bool isEndOfBatch) {
dispatchCallbacksToJava(executorToken, weakCallback, weakCallbackQueueThread, std::move(calls), isEndOfBatch);
};
auto bridgeCallback = folly::make_unique<PlatformBridgeCallback>(weakCallback, weakCallbackQueueThread);
auto nativeExecutorFactory = extractRefPtr<CountableJSExecutorFactory>(env, executor);
auto executorTokenFactory = folly::make_unique<JExecutorTokenFactory>();
auto bridge = createNew<CountableBridge>(nativeExecutorFactory.get(), std::move(executorTokenFactory), bridgeCallback);
auto bridge = createNew<CountableBridge>(nativeExecutorFactory.get(), std::move(executorTokenFactory), std::move(bridgeCallback));
setCountableForJava(env, obj, std::move(bridge));
}
@ -968,6 +995,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
jclass callbackClass = env->FindClass("com/facebook/react/bridge/ReactCallback");
bridge::gCallbackMethod = env->GetMethodID(callbackClass, "call", "(Lcom/facebook/react/bridge/ExecutorToken;IILcom/facebook/react/bridge/ReadableNativeArray;)V");
bridge::gOnBatchCompleteMethod = env->GetMethodID(callbackClass, "onBatchComplete", "()V");
bridge::gOnExecutorUnregisteredMethod = env->GetMethodID(callbackClass, "onExecutorUnregistered", "(Lcom/facebook/react/bridge/ExecutorToken;)V");
jclass markerClass = env->FindClass("com/facebook/react/bridge/ReactMarker");
bridge::gLogMarkerMethod = env->GetStaticMethodID(markerClass, "logMarker", "(Ljava/lang/String;)V");