Skip to content

Commit 588f0b8

Browse files
foghinaFacebook Github Bot 7
authored and
Facebook Github Bot 7
committed
Actually close packager websocket connection when destroying instance
Summary: Ugh. We never actually closed the websocket connection. Furthermore, upon calling `closeQuietly()`, `onClose()` is called, which does `reconnect()`. Beautiful. This results in `ReactInstanceManager` leaking after calling `destroy()` and nullifying all references to it. To fix this I made sure `closeQuietly()` actually closes the connection for good, **and** made sure we actually call it when destroying an instance. Reviewed By: AaaChiuuu Differential Revision: D3835023 fbshipit-source-id: 31811805dd97b725ea5887cffed9bed49addda83
1 parent 9289e4f commit 588f0b8

File tree

3 files changed

+64
-27
lines changed

3 files changed

+64
-27
lines changed

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

+18-4
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public interface OnServerContentChangeListener {
7979
}
8080

8181
public interface PackagerCommandListener {
82-
void onReload();
82+
void onPackagerReloadCommand();
8383
}
8484

8585
public interface PackagerStatusCallback {
@@ -88,15 +88,15 @@ public interface PackagerStatusCallback {
8888

8989
private final DevInternalSettings mSettings;
9090
private final OkHttpClient mClient;
91-
private final JSPackagerWebSocketClient mPackagerConnection;
9291
private final Handler mRestartOnChangePollingHandler;
9392

9493
private boolean mOnChangePollingEnabled;
94+
private @Nullable JSPackagerWebSocketClient mPackagerConnection;
9595
private @Nullable OkHttpClient mOnChangePollingClient;
9696
private @Nullable OnServerContentChangeListener mOnServerContentChangeListener;
9797
private @Nullable Call mDownloadBundleFromURLCall;
9898

99-
public DevServerHelper(DevInternalSettings settings, final PackagerCommandListener commandListener) {
99+
public DevServerHelper(DevInternalSettings settings) {
100100
mSettings = settings;
101101
mClient = new OkHttpClient.Builder()
102102
.connectTimeout(HTTP_CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
@@ -105,18 +105,32 @@ public DevServerHelper(DevInternalSettings settings, final PackagerCommandListen
105105
.build();
106106

107107
mRestartOnChangePollingHandler = new Handler();
108+
}
109+
110+
public void openPackagerConnection(final PackagerCommandListener commandListener) {
111+
if (mPackagerConnection != null) {
112+
FLog.w(ReactConstants.TAG, "Packager connection already open, nooping.");
113+
return;
114+
}
108115
mPackagerConnection = new JSPackagerWebSocketClient(getPackagerConnectionURL(),
109116
new JSPackagerWebSocketClient.JSPackagerCallback() {
110117
@Override
111118
public void onMessage(String target, String action) {
112119
if (commandListener != null && "bridge".equals(target) && "reload".equals(action)) {
113-
commandListener.onReload();
120+
commandListener.onPackagerReloadCommand();
114121
}
115122
}
116123
});
117124
mPackagerConnection.connect();
118125
}
119126

127+
public void closePackagerConnection() {
128+
if (mPackagerConnection != null) {
129+
mPackagerConnection.closeQuietly();
130+
mPackagerConnection = null;
131+
}
132+
}
133+
120134
/** Intent action for reloading the JS */
121135
public static String getReloadAppAction(Context context) {
122136
return context.getPackageName() + RELOAD_APP_ACTION_SUFFIX;

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

+21-18
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import com.facebook.react.common.ReactConstants;
4949
import com.facebook.react.common.ShakeDetector;
5050
import com.facebook.react.common.futures.SimpleSettableFuture;
51+
import com.facebook.react.devsupport.DevServerHelper.PackagerCommandListener;
5152
import com.facebook.react.devsupport.StackTraceHelper.StackFrame;
5253
import com.facebook.react.modules.debug.DeveloperSettings;
5354

@@ -82,7 +83,7 @@
8283
* {@code <activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>}
8384
* {@code <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>}
8485
*/
85-
public class DevSupportManagerImpl implements DevSupportManager {
86+
public class DevSupportManagerImpl implements DevSupportManager, PackagerCommandListener {
8687

8788
private static final int JAVA_ERROR_COOKIE = -1;
8889
private static final int JSEXCEPTION_ERROR_COOKIE = -1;
@@ -120,7 +121,6 @@ private static enum ErrorType {
120121
private int mLastErrorCookie = 0;
121122
private @Nullable ErrorType mLastErrorType;
122123

123-
124124
private static class JscProfileTask extends AsyncTask<String, Void, Void> {
125125
private static final MediaType JSON =
126126
MediaType.parse("application/json; charset=utf-8");
@@ -178,19 +178,7 @@ public DevSupportManagerImpl(
178178
mApplicationContext = applicationContext;
179179
mJSAppBundleName = packagerPathForJSBundleName;
180180
mDevSettings = new DevInternalSettings(applicationContext, this);
181-
mDevServerHelper = new DevServerHelper(
182-
mDevSettings,
183-
new DevServerHelper.PackagerCommandListener() {
184-
@Override
185-
public void onReload() {
186-
UiThreadUtil.runOnUiThread(new Runnable() {
187-
@Override
188-
public void run() {
189-
handleReloadJS();
190-
}
191-
});
192-
}
193-
});
181+
mDevServerHelper = new DevServerHelper(mDevSettings);
194182

195183
// Prepare shake gesture detector (will be started/stopped from #reload)
196184
mShakeDetector = new ShakeDetector(new ShakeDetector.ShakeListener() {
@@ -237,8 +225,11 @@ public void handleException(Exception e) {
237225
if (e instanceof JSException) {
238226
FLog.e(ReactConstants.TAG, "Exception in native call from JS", e);
239227
// TODO #11638796: convert the stack into something useful
240-
showNewError(e.getMessage() + "\n\n" + ((JSException) e).getStack(), new StackFrame[] {},
241-
JSEXCEPTION_ERROR_COOKIE, ErrorType.JS);
228+
showNewError(
229+
e.getMessage() + "\n\n" + ((JSException) e).getStack(),
230+
new StackFrame[] {},
231+
JSEXCEPTION_ERROR_COOKIE,
232+
ErrorType.JS);
242233
} else {
243234
showNewJavaError(e.getMessage(), e);
244235
}
@@ -388,7 +379,7 @@ public void onOptionSelected() {
388379
}
389380
});
390381
options.put(
391-
mApplicationContext.getString(R.string.catalyst_element_inspector),
382+
mApplicationContext.getString(R.string.catalyst_element_inspector),
392383
new DevOptionHandler() {
393384
@Override
394385
public void onOptionSelected() {
@@ -674,6 +665,16 @@ public void isPackagerRunning(DevServerHelper.PackagerStatusCallback callback) {
674665
return mLastErrorStack;
675666
}
676667

668+
@Override
669+
public void onPackagerReloadCommand() {
670+
UiThreadUtil.runOnUiThread(new Runnable() {
671+
@Override
672+
public void run() {
673+
handleReloadJS();
674+
}
675+
});
676+
}
677+
677678
private void updateLastErrorInfo(
678679
final String message,
679680
final StackFrame[] stack,
@@ -802,6 +803,7 @@ private void reload() {
802803
mIsReceiverRegistered = true;
803804
}
804805

806+
mDevServerHelper.openPackagerConnection(this);
805807
if (mDevSettings.isReloadOnJSChangeEnabled()) {
806808
mDevServerHelper.startPollingOnChangeEndpoint(
807809
new DevServerHelper.OnServerContentChangeListener() {
@@ -841,6 +843,7 @@ public void onServerContentChanged() {
841843
mDevOptionsDialog.dismiss();
842844
}
843845

846+
mDevServerHelper.closePackagerConnection();
844847
mDevServerHelper.stopPollingOnChangeEndpoint();
845848
}
846849
}

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

+25-5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public class JSPackagerWebSocketClient implements WebSocketListener {
4040

4141
private final String mUrl;
4242
private final Handler mHandler;
43+
private boolean mClosed = false;
4344
private boolean mSuppressConnectionErrors;
4445

4546
public interface JSPackagerCallback {
@@ -57,6 +58,9 @@ public JSPackagerWebSocketClient(String url, JSPackagerCallback callback) {
5758
}
5859

5960
public void connect() {
61+
if (mClosed) {
62+
throw new IllegalStateException("Can't connect closed client");
63+
}
6064
OkHttpClient httpClient = new OkHttpClient.Builder()
6165
.connectTimeout(10, TimeUnit.SECONDS)
6266
.writeTimeout(10, TimeUnit.SECONDS)
@@ -69,6 +73,9 @@ public void connect() {
6973
}
7074

7175
private void reconnect() {
76+
if (mClosed) {
77+
throw new IllegalStateException("Can't reconnect closed client");
78+
}
7279
if (!mSuppressConnectionErrors) {
7380
FLog.w(TAG, "Couldn't connect to packager, will silently retry");
7481
mSuppressConnectionErrors = true;
@@ -77,12 +84,21 @@ private void reconnect() {
7784
new Runnable() {
7885
@Override
7986
public void run() {
80-
connect();
87+
// check that we haven't been closed in the meantime
88+
if (!mClosed) {
89+
connect();
90+
}
8191
}
82-
}, RECONNECT_DELAY_MS);
92+
},
93+
RECONNECT_DELAY_MS);
8394
}
8495

8596
public void closeQuietly() {
97+
mClosed = true;
98+
closeWebSocketQuietly();
99+
}
100+
101+
private void closeWebSocketQuietly() {
86102
if (mWebSocket != null) {
87103
try {
88104
mWebSocket.close(1000, "End of session");
@@ -151,7 +167,9 @@ public void onFailure(IOException e, Response response) {
151167
if (mWebSocket != null) {
152168
abort("Websocket exception", e);
153169
}
154-
reconnect();
170+
if (!mClosed) {
171+
reconnect();
172+
}
155173
}
156174

157175
@Override
@@ -163,7 +181,9 @@ public void onOpen(WebSocket webSocket, Response response) {
163181
@Override
164182
public void onClose(int code, String reason) {
165183
mWebSocket = null;
166-
reconnect();
184+
if (!mClosed) {
185+
reconnect();
186+
}
167187
}
168188

169189
@Override
@@ -173,6 +193,6 @@ public void onPong(Buffer payload) {
173193

174194
private void abort(String message, Throwable cause) {
175195
FLog.e(TAG, "Error occurred, shutting down websocket connection: " + message, cause);
176-
closeQuietly();
196+
closeWebSocketQuietly();
177197
}
178198
}

0 commit comments

Comments
 (0)