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); Systrace.unregisterListener(mTraceListener);
synchronouslyDisposeBridgeOnJSThread(); synchronouslyDisposeBridgeOnJSThread();
mReactQueueConfiguration.destroy();
} }
mReactQueueConfiguration.destroy();
boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0); boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
if (!wasIdle && !mBridgeIdleListeners.isEmpty()) { if (!wasIdle && !mBridgeIdleListeners.isEmpty()) {
for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) { for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
@ -293,12 +295,12 @@ public class CatalystInstanceImpl implements CatalystInstance {
@Override @Override
public void handleMemoryPressure(final MemoryPressure level) { public void handleMemoryPressure(final MemoryPressure level) {
mReactQueueConfiguration.getJSQueueThread().runOnQueue( mReactQueueConfiguration.getJSQueueThread().runOnQueue(
new Runnable() { new Runnable() {
@Override @Override
public void run() { public void run() {
Assertions.assertNotNull(mBridge).handleMemoryPressure(level); 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) { public void call(ExecutorToken executorToken, int moduleId, int methodId, ReadableNativeArray parameters) {
mReactQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread(); mReactQueueConfiguration.getNativeModulesQueueThread().assertIsOnThread();
// Suppress any callbacks if destroyed - will only lead to sadness. synchronized (mTeardownLock) {
if (mDestroyed) { // Suppress any callbacks if destroyed - will only lead to sadness.
return; if (mDestroyed) {
} return;
}
mJavaRegistry.call(CatalystInstanceImpl.this, executorToken, moduleId, methodId, parameters); mJavaRegistry.call(CatalystInstanceImpl.this, executorToken, moduleId, methodId, parameters);
}
} }
@Override @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 // 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 // still want to trigger the debug listener since it allows instrumentation tests to end and
// check their assertions without waiting for a timeout. // check their assertions without waiting for a timeout.
if (!mDestroyed) { synchronized (mTeardownLock) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchComplete"); if (!mDestroyed) {
try { Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchComplete");
mJavaRegistry.onBatchComplete(); try {
} finally { mJavaRegistry.onBatchComplete();
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE); } finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
} }
} }
decrementPendingJSCalls(); 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 { private class NativeExceptionHandler implements QueueThreadExceptionHandler {

View File

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

View File

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

View File

@ -24,6 +24,18 @@ struct dynamic;
namespace facebook { namespace facebook {
namespace react { 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 Bridge;
class ExecutorRegistration { class ExecutorRegistration {
public: public:
@ -39,15 +51,13 @@ public:
class Bridge { class Bridge {
public: public:
typedef std::function<void(ExecutorToken executorToken, std::vector<MethodCall>, bool isEndOfBatch)> Callback;
/** /**
* This must be called on the main JS thread. * This must be called on the main JS thread.
*/ */
Bridge( Bridge(
JSExecutorFactory* jsExecutorFactory, JSExecutorFactory* jsExecutorFactory,
std::unique_ptr<ExecutorTokenFactory> executorTokenFactory, std::unique_ptr<ExecutorTokenFactory> executorTokenFactory,
Callback callback); std::unique_ptr<BridgeCallback> callback);
virtual ~Bridge(); virtual ~Bridge();
/** /**
@ -128,7 +138,7 @@ public:
*/ */
void destroy(); void destroy();
private: 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(), // 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 // 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. // will have been destroyed within ~Bridge(), thus causing a SIGSEGV.

View File

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