-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Memory Leak when handling WebSocket at server side #27414
Comments
@jimmyshiau Could you say what version of the VM and what OS you're running on? Thanks! |
Dart 1.19.1 and Linux (Jimmy is out of the office) |
I've repro'd on 1.19.1, but this doesn't repro at top of tree. It also doesn't repro in 1.19.1 with tcmalloc patched in. After ~10 runs RSS is reasonably stable at about 180MB, whereas before it would grow ~10-15MB per run as in the report. @jimmyshiau @tomyeh Can you try on your end with a recent 1.20 dev? E.g.: https://storage.googleapis.com/dart-archive/channels/dev/release/1.20.0-dev.7.0/sdk/dartsdk-linux-x64-release.zip. Thanks! |
Hi @zanderso 1.20.0-dev.7.0 is better, but it still grew up about 1MB per run: 100 > 101 > 102
http://screencast.com/t/RHy2EGpO RES67652 Dart ObservatoryUint8List Instances |
Hi, any update? Can you reproduce it? Or, we mis-interpreted something here? |
When I tried at top-of-tree a couple of weeks ago, RSS stabilized eventually, but I can take another look. If there is any script you are using to drive the browser, that may help me carry out the exact same steps that you are following. |
Some background information: Please tell us know if anything we can help to clarify the issue. Thanks. |
I am assuming this issue is stale. Please reopen if more work need to happen in this area. |
No, the problem is still there. We're forced to restart the server if exceeding a limit. Please look at it. Thanks. |
The program in the first comment is what we tried to reproduce with a simple case. The real program is of course much more complicated. Here is what we have done. Hope it helps.
|
I made another simplified test code as follows. From server's log, you'll find RSS keeps growing. It seems the more concurrent connections, the faster memory use grows.
and
|
I found a workaround: cancel subscription when the WebSocket connection is closing.
Not sure if memory consumption is stabilised (I just watched it a day), but at least the growth of memory consumption is reduced from 900M to 400M after online 20 hours. It seems be cyclic reference issue. Hope this information helps. |
I'm on the latest flutter beta and latest dart. Flutter (Channel beta, 1.18.0-11.1.pre, on Linux, locale en_US.UTF-8) I'm seeing a massive memory leak using websockets. I'm sending a 1gb file to my shelf server and I dart observatory shows 3gb of _Uint8Lists leaking. If I close the server, a ton of ram is released, but the server runs all the time, I can't close it everytime someone uploads a file. What can I do? My server runs in it's own isolate if that matters. The server is very simple. It receives the UInt8List data and writes it to a file. |
@sgehrman Did you try Dart 2.8 at server side? Based on our experiences, the memory usage is much better than older versions. Our server can run days under heavy load without significant memory surge. However, we don't use Dart 2.9 in production system yet, so no sure if any difference. |
@sgehrman can you share a small code example for the situation you describe that causes that much memory to be retained. (for example like the one in #27414 (comment)) |
@tomyeh The server is running in my flutter mobile app. I have a feature where my app hosts a web page locally and uses can view and drag and drop files from their browser on the desktop. The files are send with websockets sendBlob. So, I don't know how easy it is to downgrade dart. I read Dart version is tied to the Flutter SDK? @zanderso I'm using all these packages like shelf, shelf_web_socket, web_socket_channel. It's not super easy to make a small code example. The code is very simplistic. The browser side:
The server side:
If I posted an apk would that be enough to debug it on your end? I could also add you as a github collaborator if you wanted to get the code and run the app. The code is private. Hoping to release it in a few weeks. |
@sgehrman Hi, I'd like to help! If you can also try your code with Http? Package_http is very easy to use. I just wanna make sure the websocket is indeed the culprit. |
I have similar problems as reported here, after extensive testing, I found no issues running locally while monitoring with Dart DevTools. However, in a Docker container, memory issues arise if the client does not explicitly call Note that it also seems like the docker container keeps the reported memory usage relatively constant after new allocations. E.g. I'm not seeing big reductions in memory usage. But when running locally both in JIT and AOT mode I observe that the process gets a big reduction of memory usage now and then. Another peculiar fact is that the size of the data sent on the socket also seems to impact how fast I can reach the problems. Dart version: 3.5 I used the code by @sgehrman for my testing, and the official docker example (https://hub.docker.com/_/dart): import "dart:async";
import "dart:io";
void main() {
HttpServer.bind(InternetAddress.anyIPv4, 8081).then((server) {
server.listen((request) {
int count = 0;
late StreamSubscription subscription;
WebSocketTransformer.upgrade(request).then((socket) {
socket.pingInterval = const Duration(seconds: 1);
subscription = socket.listen(
(dynamic data) {
count += (data as List).length;
},
onDone: () async {
print(
'Done: $count; rss=${ProcessInfo.currentRss ~/ (1024 * 1024)}',
);
await subscription.cancel(); //This solution does not help
},
);
return socket.done;
});
});
});
} Client code import "dart:io";
import "dart:async";
final String data = '<a long string>';
main() async {
for (int i = 0; i++ < 100;) {
final ops = <Future>[];
for (int j = 0; j++ < 100;) {
ops.add(WebSocket.connect("ws://localhost:8081").then((socket) {
for (int k = 0; k++ < 100;) {
socket.add(data.codeUnits);
}
//socket.close(); // If this call is not done we get memory problems
}));
}
print('awaiting ops');
await Future.wait(ops);
}
exit(0);
} Dockerfile FROM dart:stable AS build
WORKDIR /app
COPY pubspec.* ./
RUN dart pub get
COPY . .
RUN dart pub get --offline
RUN dart compile exe lib/websocket_server.dart -o bin/server
FROM scratch
COPY --from=build /runtime/ /
COPY --from=build /app/bin/server /app/bin/
EXPOSE 8081
CMD ["/app/bin/server"] |
I took a look at this today using the code provided by @Isakdl above. It clearly was leaking for me inside and outside of the Docker container: what I saw is This seems to be happening because something is wrong with shutdown sequence: we shutdown read direction on the socket first, and then we shutdown write direction - but then we don't actually recognize that both directions are now shutdown and we need to close and destroy the socket. And the socket is left lingering in the zombie state. The code in void shutdownWrite() {
if (!isClosing && !isClosed) {
if (closedReadEventSent) {
close();
} else {
sendToEventHandler(1 << shutdownWriteCommand);
}
isClosedWrite = true;
}
} We arrive here with UPDATE: My initial analysis was incorrect here. It is by design that shutting down read direction does not simply discard the data and instead expects the reader to drain the socket before dispatching |
This CL should fix the leak of While the leak seems to be fixed with my CL, I still observe somewhat suboptimal behavior from the GC - we don't seem to trigger mark sweep GC often enough which means we keep accumulating some external memory (IO buffers?) which causes RSS to grow. @rmacnak-google could you take a look if we could tweak our GC heuristics here? You would need to apply my CL to fix the leak and then just run the code from the comment above (no Docker is needed to reproduce this). |
- Don't treat heap_growth_max as a lower bound when using the ratio heuristic. For small heaps this can cause us to exceed the working set size by more than 10x. - Stick with the ratio heuristic when we're staying under the desired time fraction. - Avoid tiny initial thresholds by growing at least by one new-space. TEST=golem Bug: #27414 Change-Id: I64b2994ea9a32ce8a8bbec15cd89d8b46a01b1bb Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/397460 Reviewed-by: Slava Egorov <[email protected]> Commit-Queue: Ryan Macnak <[email protected]>
@mraleph Good job! Thank you so much for this! 😀 👍 👏 |
Simple test case
Server RES
66080
133060
147224
193972
193932
224692
245348
256536
278660
259632
261072
281472
261316
263444
266912
266896
287424
287484
288996
304852
345812
347388
358788
At each run, I opened 100 browser tabs and then close them all. Also, before checking memory use, I ran GC first.
The text was updated successfully, but these errors were encountered: