[Android] Heap snapshot via Hermes CDP fails mid-stream: OkHttp WebSocket closes with code 1001 due to MAX_QUEUE_SIZE overflow in CxxInspectorPackagerConnection
还没有人认领这个 Issue。
- 主要语言
- C++
- 星标
- 127k
- 派生
- 25.3k
- 平均合并
- 1 天 23 小时
- 30 天内合并 PR
- 4
描述
Description
The following issue was found using the Claude Opus 4.6 LLM and verified by me manually. I've used Claude to come up with a fix in the React Native Java code, then monkey patched the RN source files and built an app with that fix.
Before the fix whenever I tried to get the snapshot through the Debugger, Chrome Dev Tools or even a custom Node script it would fail. When using the node script I would get around 70MB of snapshot and then the WS connection would close leaving me with an incomplete snapshot file. By inspecting the internal metro code during that I found that the socket connection to the device was closing with the 1001 code.
After applying the fix Claude came up with both the Debugger and the Node script were able to get the snapshot without any issues.
The final snapshot I extracted from the app was around 120 MB.
I've confirmed this issue to be present in the 0.83. which is the latest version I am able to upgrade the project in which I ran into this issue. The fix that ended up helping I only validated on 0.79.6
Summary generated by Claude
React Native version: 0.79.6 (unpatched as of main branch)
Platform: Android only
Description
Taking a JS heap snapshot from a React Native Android app via Hermes CDP always fails for large heaps (>100MB snapshot size). The WebSocket connection between the app and Metro closes with code 1001 partway through the snapshot — typically after a portion of chunks are delivered — causing the snapshot to be incomplete or the tooling to throw WS closed before snapshot read was completed.
Root cause
CxxInspectorPackagerConnection.java uses OkHttp to maintain the WebSocket from the app to Metro. OkHttp's RealWebSocket has a hardcoded MAX_QUEUE_SIZE = 16 * 1024 * 1024 (16 MiB). When HeapProfiler.takeHeapSnapshot is triggered, Hermes streams chunks to the inspector faster than TCP delivers them to Metro. The outgoing OkHttp queue fills past 16 MiB, and OkHttp closes the connection with code 1001.
A second issue compounds it: the OkHttpClient is configured with writeTimeout(10, TimeUnit.SECONDS). Since heap snapshots can take more than 10 seconds to fully flush, the write timeout can also trigger disconnection independently.
Relevant file: ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.java
// Current code (unfixed):
private final OkHttpClient mHttpClient =
new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS) // ← kills long snapshots
.readTimeout(0, TimeUnit.MINUTES)
.build();
// ...in connectWebSocket():
return new IWebSocket() {
@Override
public void send(String message) {
webSocket.send(message); // ← no backpressure, overflows MAX_QUEUE_SIZE
}
// ...
};
Fix
Two changes in connectWebSocket():
- Set writeTimeout(0, TimeUnit.SECONDS) to disable the write timeout (matching how readTimeout is already disabled).
- Introduce an unbounded local queue with a sender thread that throttles sends to keep OkHttp's internal queue below its 16 MiB hard limit:
.writeTimeout(0, TimeUnit.SECONDS) // was 10s — disabled to match readTimeout
// In connectWebSocket(), replace direct webSocket.send() with:
final BlockingQueue<String> localQueue = new LinkedBlockingQueue<>();
final AtomicBoolean closing = new AtomicBoolean(false);
final long OKHTTP_QUEUE_HIGH_WATERMARK = 8 * 1024 * 1024; // stay under 16 MiB hard limit
Thread senderThread = new Thread(() -> {
while (!closing.get()) {
try {
String message = localQueue.poll(1, TimeUnit.SECONDS);
if (message == null) continue;
while (webSocket.queueSize() > OKHTTP_QUEUE_HIGH_WATERMARK) {
Thread.sleep(5);
}
webSocket.send(message);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "InspectorWebSocketSender");
senderThread.setDaemon(true);
senderThread.start();
return new IWebSocket() {
@Override public void send(String message) { localQueue.offer(message); }
@Override public void close() {
closing.set(true);
webSocket.close(1000, "End of session");
}
};
Steps to reproduce
- Run a React Native Android app in debug mode with a large heap
- Connect to Metro via CDP and call HeapProfiler.takeHeapSnapshot
- Observe connection closes before the whole snapshot is delivered. code 1001 is given by the socket (node_modules/@react-native/dev-middleware/dist/inspector-proxy/InspectorProxy.js:232)
React Native Version
0.83.0
Output of npx @react-native-community/cli info
System:
OS: Linux 6.18 Manjaro Linux
CPU: (8) x64 Intel(R) Core(TM) i7-10610U CPU @ 1.80GHz
Memory: 2.76 GB / 30.97 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 22.22.0
path: /home/owner/.nvm/versions/node/v22.22.0/bin/node
Yarn:
version: 1.22.22
path: /usr/local/bin/yarn
npm:
version: 10.9.4
path: /home/owner/.nvm/versions/node/v22.22.0/bin/npm
Watchman: Not Found
SDKs:
Android SDK:
Android NDK: 29.0.14206865
IDEs:
Android Studio: AI-253.30387.90.2532.14935130
Languages:
Java:
version: 25.0.2
path: /usr/bin/javac
Ruby:
version: 3.4.8
path: /usr/bin/ruby
npmPackages:
"@react-native-community/cli": Not Found
react:
installed: 19.2.0
wanted: 19.2.0
react-native:
installed: 0.83.0
wanted: 0.83.0
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: Not found
newArchEnabled: Not found
iOS:
hermesEnabled: Not found
newArchEnabled: Not found
Screenshots and Videos
No response
Maybe related to
https://github.com/facebook/react-native/issues/39651
https://github.com/facebook/react-native/issues/49158
Repro
This would likely be possible to be reproduced with this: https://github.com/abbasvlb/heapdumpissue/
although I would use a higher value than 20MB
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
调研方向
从 ReactAndroid/src/main/java/com/facebook/react/devsupport/CxxInspectorPackagerConnection.java 开始,重点检查 OkHttpClient 配置和 connectWebSocket()。通过在较大的堆上调用 HeapProfiler.takeHeapSnapshot,使用链接的 heapdumpissue 项目重现该问题。完成的标准是 Android CDP 连接保持打开状态并传输完整的快照,而不是以代码 1001 关闭。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- android, java
- 领域
- mobile-dev
- Issue 类型
- 缺陷
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 活跃度
- 冷清
- 描述清晰度
- 描述清楚
- 新手友好度
- 55/100