Skip to content

Commit e9aab0d

Browse files
cwdickfacebook-github-bot
authored andcommitted
Add locking around CatalystInstance.getJavaScriptContext()
Reviewed By: kathryngray Differential Revision: D5861693 fbshipit-source-id: 226ff15622d5e1a8ae3ad4c63f1434bd95c1fa21
1 parent c1058b1 commit e9aab0d

File tree

5 files changed

+66
-19
lines changed

5 files changed

+66
-19
lines changed

ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@
99

1010
package com.facebook.react.bridge;
1111

12-
import javax.annotation.Nullable;
13-
14-
import java.util.Collection;
15-
1612
import com.facebook.proguard.annotations.DoNotStrip;
1713
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
1814
import com.facebook.react.common.annotations.VisibleForTesting;
15+
import java.util.Collection;
16+
import javax.annotation.Nullable;
1917

2018
/**
2119
* A higher level API on top of the asynchronous JSC bridge. This provides an
@@ -93,6 +91,10 @@ void callFunction(
9391

9492
/**
9593
* Get the C pointer (as a long) to the JavaScriptCore context associated with this instance.
94+
*
95+
* <p>Use the following pattern to ensure that the JS context is not cleared while you are using
96+
* it: JavaScriptContextHolder jsContext = reactContext.getJavaScriptContextHolder()
97+
* synchronized(jsContext) { nativeThingNeedingJsContext(jsContext.get()); }
9698
*/
97-
long getJavaScriptContext();
99+
JavaScriptContextHolder getJavaScriptContextHolder();
98100
}

ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ public String toString() {
9191
private boolean mJSBundleHasLoaded;
9292
private @Nullable String mSourceURL;
9393

94+
private JavaScriptContextHolder mJavaScriptContextHolder;
95+
9496
// C++ parts
9597
private final HybridData mHybridData;
9698
private native static HybridData initHybrid();
@@ -126,6 +128,8 @@ private CatalystInstanceImpl(
126128
mNativeModuleRegistry.getJavaModules(this),
127129
mNativeModuleRegistry.getCxxModules());
128130
Log.d(ReactConstants.TAG, "Initializing React Xplat Bridge after initializeBridge");
131+
132+
mJavaScriptContextHolder = new JavaScriptContextHolder(getJavaScriptContext());
129133
}
130134

131135
private static class BridgeCallback implements ReactCallback {
@@ -333,6 +337,11 @@ public void run() {
333337
public void run() {
334338
// Kill non-UI threads from neutral third party
335339
// potentially expensive, so don't run on UI thread
340+
341+
// contextHolder is used as a lock to guard against other users of the JS VM having
342+
// the VM destroyed underneath them, so notify them before we resetNative
343+
mJavaScriptContextHolder.clear();
344+
336345
mHybridData.resetNative();
337346
getReactQueueConfiguration().destroy();
338347
Log.d(ReactConstants.TAG, "CatalystInstanceImpl.destroy() end");
@@ -436,7 +445,11 @@ public void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener l
436445
public native void setGlobalVariable(String propName, String jsonValue);
437446

438447
@Override
439-
public native long getJavaScriptContext();
448+
public JavaScriptContextHolder getJavaScriptContextHolder() {
449+
return mJavaScriptContextHolder;
450+
}
451+
452+
private native long getJavaScriptContext();
440453

441454
private void incrementPendingJSCalls() {
442455
int oldPendingCalls = mPendingJSCalls.getAndIncrement();
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2004-present Facebook. All Rights Reserved.
2+
3+
package com.facebook.react.bridge;
4+
5+
import javax.annotation.concurrent.GuardedBy;
6+
7+
/**
8+
* Wrapper for JavaScriptContext native pointer. CatalystInstanceImpl creates this on demand, and
9+
* will call clear() before destroying the VM. People who need the raw JavaScriptContext pointer
10+
* can synchronize on this wrapper object to guarantee that it will not be destroyed.
11+
*/
12+
public class JavaScriptContextHolder {
13+
@GuardedBy("this")
14+
private long mContext;
15+
16+
public JavaScriptContextHolder(long context) {
17+
mContext = context;
18+
}
19+
20+
@GuardedBy("this")
21+
public long get() {
22+
return mContext;
23+
}
24+
25+
public synchronized void clear() {
26+
mContext = 0;
27+
}
28+
29+
}

ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,19 @@
99

1010
package com.facebook.react.bridge;
1111

12-
import javax.annotation.Nullable;
13-
14-
import java.lang.ref.WeakReference;
15-
import java.util.concurrent.CopyOnWriteArraySet;
16-
1712
import android.app.Activity;
1813
import android.content.Context;
1914
import android.content.ContextWrapper;
2015
import android.content.Intent;
2116
import android.os.Bundle;
2217
import android.view.LayoutInflater;
23-
2418
import com.facebook.infer.annotation.Assertions;
2519
import com.facebook.react.bridge.queue.MessageQueueThread;
2620
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
2721
import com.facebook.react.common.LifecycleState;
22+
import java.lang.ref.WeakReference;
23+
import java.util.concurrent.CopyOnWriteArraySet;
24+
import javax.annotation.Nullable;
2825

2926
/**
3027
* Abstract ContextWrapper for Android application or activity {@link Context} and
@@ -372,9 +369,12 @@ public boolean startActivityForResult(Intent intent, int code, Bundle bundle) {
372369
}
373370

374371
/**
375-
* Get the C pointer (as a long) to the JavaScriptCore context associated with this instance.
372+
* Get the C pointer (as a long) to the JavaScriptCore context associated with this instance. Use
373+
* the following pattern to ensure that the JS context is not cleared while you are using it:
374+
* JavaScriptContextHolder jsContext = reactContext.getJavaScriptContextHolder()
375+
* synchronized(jsContext) { nativeThingNeedingJsContext(jsContext.get()); }
376376
*/
377-
public long getJavaScriptContext() {
378-
return mCatalystInstance.getJavaScriptContext();
377+
public JavaScriptContextHolder getJavaScriptContextHolder() {
378+
return mCatalystInstance.getJavaScriptContextHolder();
379379
}
380380
}

ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.facebook.react.bridge.CatalystInstance;
3131
import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler;
3232
import com.facebook.react.bridge.JavaJSExecutor;
33+
import com.facebook.react.bridge.JavaScriptContextHolder;
3334
import com.facebook.react.bridge.ReactContext;
3435
import com.facebook.react.bridge.ReactMarker;
3536
import com.facebook.react.bridge.ReactMarkerConstants;
@@ -711,10 +712,12 @@ public void run() {
711712
return;
712713
}
713714
try {
714-
long jsContext = mCurrentContext.getJavaScriptContext();
715-
Class clazz = Class.forName("com.facebook.react.packagerconnection.SamplingProfilerPackagerMethod");
716-
RequestHandler handler = (RequestHandler)clazz.getConstructor(long.class).newInstance(jsContext);
717-
handler.onRequest(null, responder);
715+
JavaScriptContextHolder jsContext = mCurrentContext.getJavaScriptContextHolder();
716+
synchronized (jsContext) {
717+
Class clazz = Class.forName("com.facebook.react.packagerconnection.SamplingProfilerPackagerMethod");
718+
RequestHandler handler = (RequestHandler)clazz.getConstructor(long.class).newInstance(jsContext.get());
719+
handler.onRequest(null, responder);
720+
}
718721
} catch (Exception e) {
719722
// Module not present
720723
}

0 commit comments

Comments
 (0)