-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Per-Message Deflate Memory Leak #1617
Comments
If I understand correctly the issue now is that it keeps stuff in its queue indefinitely due to never completing jobs (because |
See also discussion on #1369 |
@lpinca hi! I read both issues and the history behind it, but yes basically the issue that jobs are never completing and this way we don't clean up properly upon disconnection. |
Are you sure this happens with inflate streams and not deflate? Asking because due to how the WebSocket is closed, For deflate streams instead |
@lpinca this happens on deflate, sorry |
Ok it makes sense and I can see why jobs might never complete. This also made things worse Lines 424 to 432 in fa99173
but reverting it would cause other problems. I'm not sure how to properly fix this. |
@lpinca in my tests the flush callback is never called after you call |
If you are using Node.js 10 it is expected, it was called before. Anyway we have to store the callback somewhere and call it when the final Line 315 in fa99173
if |
Thank you for the report. If you want to try to fix it please do. I'm not sure when I can do it. |
@lpinca I have some fix currently and I don't like it. Which is putting the callback in inflate as well, and on A better fix IMO is to have a class which does deflate/inflate and have in its queue - data (to compress) and promise, this way we don't have a direct reference to WebSocket. I'll play with this approach and update you |
It is not a big problem. There is one |
@lpinca how do you guarantee that you have single write at the time? |
It's done in https://github.com/websockets/ws/blob/master/lib/sender.js, we queue writes. Lines 266 to 267 in fa99173
|
and what about reads (deflate) ? |
They should not have the issue described here because |
Cool @lpinca ! I will make a PR. I started re-making my old, the question is what should I pass in the arguments to the callback, passing data their I think is in-correct, I think I should pass |
I would just call it without arguments. This callback Line 315 in fa99173
must not be called after We only have to ensure this Line 314 in fa99173
is called to signal that the job completed. |
Umm, I actually thought about adding code in I am not sure how can I call the |
Yes do in |
Oh, you want here - Line 314 in fa99173
something like:
and in cleanup - call the callback like |
Something like this: diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js
index 29881b3..96fcf8b 100644
--- a/lib/permessage-deflate.js
+++ b/lib/permessage-deflate.js
@@ -134,6 +134,10 @@ class PerMessageDeflate {
if (this._deflate) {
this._deflate.close();
+ if (this._deflate.cb) {
+ this._doNotCallOuterCallback = true;
+ this._deflate.cb();
+ }
this._deflate = null;
}
}
@@ -312,6 +316,8 @@ class PerMessageDeflate {
zlibLimiter.push((done) => {
this._compress(data, fin, (err, result) => {
done();
+ if (this._doNotCallOuterCallback) return;
+
callback(err, result);
});
}); with |
make sense, thanks I'll PR later! |
Here is something cleaner without using an additional flag: diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js
index 29881b3..204816e 100644
--- a/lib/permessage-deflate.js
+++ b/lib/permessage-deflate.js
@@ -134,6 +134,7 @@ class PerMessageDeflate {
if (this._deflate) {
this._deflate.close();
+ if (this._deflate[kCallback] !== null) this._deflate[kCallback]();
this._deflate = null;
}
}
@@ -312,7 +313,7 @@ class PerMessageDeflate {
zlibLimiter.push((done) => {
this._compress(data, fin, (err, result) => {
done();
- callback(err, result);
+ if (err || result) callback(err, result);
});
});
}
@@ -419,6 +420,8 @@ class PerMessageDeflate {
this._deflate.on('data', deflateOnData);
}
+ this._deflate[kCallback] = callback;
+
this._deflate.write(data);
this._deflate.flush(zlib.Z_SYNC_FLUSH, () => {
if (!this._deflate) {
@@ -431,6 +434,8 @@ class PerMessageDeflate {
return;
}
+ this._deflate[kCallback] = null;
+
let data = bufferUtil.concat(
this._deflate[kBuffers],
this._deflate[kTotalLength] |
@lpinca liked your approach, removing the flag and updating the PR. |
I'm not sure if this is related, but I'm having an issue where after having the ws server running for a few days, suddenly the server just starts ignoring all messages from the client (except for the connection message). After 4 hours of debugging, I finally found that the messages disappear in the call to decompress because they are just pushed into the Now in general, it seems insane to me to do decompression of like 20byte segments asynchronously?? but ignoring that I have no idea why this happens. Is this something that might be fixed with this patch? I'm using version [email protected] that does not contain it. |
@phiresky the scenario I wrote in the PR is the same as yours, please try to upgrade to latest version and report back. |
sorry for not following the issue template, this way is easier to explain the issue, I will be happy to elaborate more
Description
In a production server after running a long time / mass-disconnection, I have noticed memory leak according to zlib & websockets.
This happens to all servers, but extremely happened to one of our servers which got mass-disconnect from 2.5k connected users to 50 connceted users. memory dump on this server showed:
WebSocket
classI managed to reproduce locally via:
WebSocket
classes in memory.Root cause
Because of AsyncLimiter have 10 pending jobs that never finished, this happens as a result of a race - calling
close
on inflate/deflate won't close any further flushes.Example:
I made some patches in the code to make sure I call the callback, but I didn't like the solution of attaching a callback to inflate/deflate objects, since the object can handle multiple operations at the same time and we can have further racers.
In my opinion, we should decouple the inflate/deflate from per-message deflate and create a class which will have an internal queue and call to inflate/deflate, one cleanup it will flush the queue - this way we can the solve the issues I wrote above.
I can submit a pull request for the above approach.
Reproducible in:
Memory dump screenshot
The picture above is a memory dump for the server which had mass-disconnect from 2.5k to 50 sockets:
The text was updated successfully, but these errors were encountered: