Skip to content

Commit

Permalink
Queue JS calls that come in before JS bundle has started loading inst…
Browse files Browse the repository at this point in the history
…ead of crashing

Summary: This mimics (some of) the behavior we have on iOS where if you call a JS module method before the JS bundle has started loading, we just queue up those calls and execute them after the bundle has started loading.

Reviewed By: javache

Differential Revision: D4117581

fbshipit-source-id: 58c5a6f87aeeb86083385334d92f2716a0574ba1
  • Loading branch information
astreet authored and Facebook Github Bot committed Nov 3, 2016
1 parent 52d90da commit 68aeffe
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ void callFunction(
*/
void destroy();
boolean isDestroyed();
boolean isAcceptingCalls();

/**
* Initialize all the native modules
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public class ReactContext extends ContextWrapper {

private static final String EARLY_JS_ACCESS_EXCEPTION_MESSAGE =
"Tried to access a JS module before the React instance was fully set up. Calls to " +
"ReactContext#getJSModule should be protected by ReactContext#hasActiveCatalystInstance().";
"ReactContext#getJSModule should only happen once initialize() has been called on your " +
"native module.";

private final CopyOnWriteArraySet<LifecycleEventListener> mLifecycleEventListeners =
new CopyOnWriteArraySet<>();
Expand Down Expand Up @@ -143,7 +144,7 @@ public CatalystInstance getCatalystInstance() {
}

public boolean hasActiveCatalystInstance() {
return mCatalystInstance != null && mCatalystInstance.isAcceptingCalls();
return mCatalystInstance != null && !mCatalystInstance.isDestroyed();
}

public LifecycleState getLifecycleState() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import javax.annotation.Nullable;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
Expand Down Expand Up @@ -57,6 +58,25 @@ public class CatalystInstanceImpl implements CatalystInstance {

private static final AtomicInteger sNextInstanceIdForTrace = new AtomicInteger(1);

private static class PendingJSCall {

public ExecutorToken mExecutorToken;
public String mModule;
public String mMethod;
public NativeArray mArguments;

public PendingJSCall(
ExecutorToken executorToken,
String module,
String method,
NativeArray arguments) {
mExecutorToken = executorToken;
mModule = module;
mMethod = method;
mArguments = arguments;
}
}

// Access from any thread
private final ReactQueueConfigurationImpl mReactQueueConfiguration;
private final CopyOnWriteArrayList<NotThreadSafeBridgeIdleDebugListener> mBridgeIdleListeners;
Expand All @@ -67,6 +87,8 @@ public class CatalystInstanceImpl implements CatalystInstance {
private final TraceListener mTraceListener;
private final JavaScriptModuleRegistry mJSModuleRegistry;
private final JSBundleLoader mJSBundleLoader;
private final ArrayList<PendingJSCall> mJSCallsPendingInit = new ArrayList<PendingJSCall>();
private final Object mJSCallsPendingInitLock = new Object();
private ExecutorToken mMainExecutorToken;

private final NativeModuleRegistry mJavaRegistry;
Expand Down Expand Up @@ -168,10 +190,20 @@ public void runJSBundle() {
mJSBundleHasLoaded = true;
// incrementPendingJSCalls();
mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
// Loading the bundle is queued on the JS thread, but may not have
// run yet. It's save to set this here, though, since any work it
// gates will be queued on the JS thread behind the load.
mAcceptCalls = true;

synchronized (mJSCallsPendingInitLock) {
// Loading the bundle is queued on the JS thread, but may not have
// run yet. It's save to set this here, though, since any work it
// gates will be queued on the JS thread behind the load.
mAcceptCalls = true;

for (PendingJSCall call : mJSCallsPendingInit) {
callJSFunction(call.mExecutorToken, call.mModule, call.mMethod, call.mArguments);
}
mJSCallsPendingInit.clear();
}


// This is registered after JS starts since it makes a JS call
Systrace.registerListener(mTraceListener);
}
Expand All @@ -193,7 +225,13 @@ public void callFunction(
return;
}
if (!mAcceptCalls) {
throw new RuntimeException("Attempt to call JS function before JS bundle is loaded.");
// Most of the time the instance is initialized and we don't need to acquire the lock
synchronized (mJSCallsPendingInitLock) {
if (!mAcceptCalls) {
mJSCallsPendingInit.add(new PendingJSCall(executorToken, module, method, arguments));
return;
}
}
}

callJSFunction(executorToken, module, method, arguments);
Expand Down Expand Up @@ -244,11 +282,6 @@ public boolean isDestroyed() {
return mDestroyed;
}

@Override
public boolean isAcceptingCalls() {
return !mDestroyed && mAcceptCalls;
}

/**
* Initialize all the native modules
*/
Expand Down

0 comments on commit 68aeffe

Please sign in to comment.