-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
reset compressor/decompressor instead of re-initialize #1840
Conversation
I don't know either and no, I don't think existing tests cover this condition but the patch seems good. We should just verify that resetting has the same behavior of re-instantiating. |
lib/permessage-deflate.js
Outdated
this._inflate[kTotalLength] = 0; | ||
this._inflate[kBuffers] = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can you move this before the if
statement?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/permessage-deflate.js
Outdated
this._deflate[kTotalLength] = 0; | ||
this._deflate[kBuffers] = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and done
Thank you. I'll cut a new release later and eventually revert if side effects will be reported. |
Thank you as well, ill keep an eye on the issues list once the new build is out. |
@schoudhary21 I see you are running into this while using nw.js (it might be worth listing the version for it as well), are you able to reproduce the same crash in a regular node.js ? |
@cTn-dev i am using https://dl.nwjs.io/v0.51.1/nwjs-sdk-v0.51.1-win-x64.zip version of nw.js, for connecting localhost websocket server using 127.0.0.1 ip address. I never had an issue with ws module. after recent update of ws, it stopped working. it will be great if it can be fixed asap; other npm modules as 'engine.io' etc are using it so; many components with different versions of ws are going to give more problem. for ws init; options i am using: options.perMessageDeflate = { for sending i am using options: thank you :) |
@schoudhary21 are you able to consistently reproduce the issue and write a test case? |
yes, every time. |
Ok, would you be so kind to write some code here, using only |
i did another test; **> if (fin && this.params[
with this .... **> if (fin && this.params[
and now the app started working fine without any issue. |
@schoudhary21 it would be great if you could write something like this: const WebSocket = require('ws');
const server = new WebSocket.Server(
{
perMessageDeflate: true,
port: 0
},
function () {
const ws = new WebSocket(`ws://localhost:${server.address().port}`, {
perMessageDeflate: {
clientNoContextTakeover: true,
threshold: 0
}
});
ws.on('open', function () {
ws.send('A'.repeat(50));
});
}
);
server.on('connection', function (ws) {
ws.on('message', console.log);
ws.on('error', console.error);
}); with the missing bits that make it crash. |
ok i will try for that; another key point it connects to my websocket server and don't crash and stay connected for long time but as soon as i try to send msg which is compression enabled then it crashes. |
@jeffrson can you please log the handshake response headers and the data received? You can do that like this: const ws = new WebSocket('ws://127.0.0.1:8888/svc');
ws.on('upgrade', function(res) {
console.log(res.headers);
});
ws.onopen = function() {
ws._socket.on('data', console.log);
}; I have some problems installing dotnet and compiling the server of https://github.com/jeffrson/node_zlib_binding_err_internal_assertion on this machine. |
No problem
|
What about the headers? I need those to see how the permessage-deflate extension is negotiated. |
I used the wrong event in the above example, sorry. It's the |
Yes, I copied the snippet from the email notification... never mind!
|
Thank you. Will test it in a bit. |
BTW, if you need to test and have a Linux version of dotnet (https://docs.microsoft.com/de-de/dotnet/core/install/linux-debian), you may create a project like so:
This will work on Windows too with .Net SDK only (I strongly assume, but I have an installation of Visual Studio) |
I can reproduce. Here is a test case: const crypto = require('crypto');
const http = require('http');
const WebSocket = require('ws');
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
const data = Buffer.from('c10b33343236313533b7000000', 'hex');
const server = http.createServer();
server.on('upgrade', (req, socket) => {
const key = crypto
.createHash('sha1')
.update(req.headers['sec-websocket-key'] + GUID)
.digest('base64');
socket.on('error', console.error);
socket.on('data', function () {
socket.write(data);
socket.write(data);
});
socket.write(
[
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
`Sec-WebSocket-Accept: ${key}`,
'Sec-Websocket-Extensions: permessage-deflate; ' +
'client_no_context_takeover; server_no_context_takeover',
'\r\n'
].join('\r\n')
);
});
server.listen(function () {
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
ws.on('open', function () {
ws.send('foo');
});
ws.on('message', console.log);
}); One thing I noticed is that our server behaves slightly differently const WebSocket = require('ws');
const server = new WebSocket.Server(
{
perMessageDeflate: {
clientNoContextTakeover: true,
serverNoContextTakeover: true,
threshold: 0
},
port: 0
},
function () {
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
ws.on('open', function () {
ws._socket.on('data', console.log);
ws.send('foo');
});
ws.on('message', console.log);
}
);
server.on('connection', function (ws) {
ws.on('error', console.error);
ws.on('message', function (message) {
console.log(message);
ws.send('12345678');
ws.send('12345678');
});
}); The frame with I will dig deeper later. |
Here is a test case that reproduces the issue using only the Node.js const zlib = require('zlib');
const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
const data = Buffer.from([
0x33,
0x34,
0x32,
0x36,
0x31,
0x35,
0x33,
0xb7,
0x00,
0x00,
0x00
]);
const inflate = zlib.createInflateRaw({
windowBits: zlib.Z_DEFAULT_WINDOWBITS
});
inflate.resume();
inflate.on('error', console.error);
inflate.write(data);
inflate.write(TRAILER);
inflate.flush(function () {
inflate.reset();
inflate.write(data);
inflate.write(TRAILER);
inflate.flush(function() {
inflate.reset();
})
}); If the second
so it seems the stream already ended when the first This is a bug in |
Something like this diff --git a/lib/permessage-deflate.js b/lib/permessage-deflate.js
index 74bf14a..a8974b9 100644
--- a/lib/permessage-deflate.js
+++ b/lib/permessage-deflate.js
@@ -376,11 +376,16 @@ class PerMessageDeflate {
this._inflate[kTotalLength]
);
- this._inflate[kTotalLength] = 0;
- this._inflate[kBuffers] = [];
+ if (this._inflate._readableState.endEmitted) {
+ this._inflate.close();
+ this._inflate = null;
+ } else {
+ this._inflate[kTotalLength] = 0;
+ this._inflate[kBuffers] = [];
- if (fin && this.params[`${endpoint}_no_context_takeover`]) {
- this._inflate.reset();
+ if (fin && this.params[`${endpoint}_no_context_takeover`]) {
+ this._inflate.reset();
+ }
}
callback(null, data); should fix the issue but I have to add a regression test and check if it works on all supported Node.js versions. I'll try to do that tomorrow. |
Oh - great work! I had only tested with some of zlib options of WebSocket.Server but could not reproduce. |
@lpinca Is this an "expected" behavior of zlib triggered by the trailer shown above that some other WS libraries use ? |
@cTn-dev yes, see #1669 (comment). See also nodejs/node@28db96f and nodejs/node@0e89b64. The test cases above (#1840 (comment) and #1840 (comment)) should work without errors on Node.js < 12. |
@schoudhary21 @jeffrson the issue should be fixed in |
Yes, it's fixed here. Thank you! |
Use
zlib.reset()
to (as per nodejs documentationReset the compressor/decompressor to factory defaults. Only applicable to the inflate and deflate algorithms.
)Recently we have introduced/enabled
server_no_context_takeover
on one of our canary production servers which i monitor with nodejs with the help of this ws library (approx 250 connections on a single nodejs process connecting to the server).With the introduction of
server_no_context_takeover
i have noticed significantly increased CPU usage on the client of approx 2x (8% -> 16%) to what it was before the parameter was added).I have traced it down to significantly higher GC activity, which went from ~100ms total time/minute to ~500ms.
When it comes to actual GC counts, it went from ~25 GC/minute to ~150.
Minor GCs almost stopped and majority of the time was spent in the major GCs and incremental GCs.
The root cause of this overhead appears to be the constant destruction/re-initialization of the
zlib.createInflateRaw
.The attached changes eliminate this overhead, and brings the performance back in line to where it was before the
server_no_context_takeover
parameter was introduced.The changes are holding up fine in production/i haven't encountered any issues so far.
I don't know if there are any potential side-effects of the below changes, the changes do pass all the tests currently available for the library (although i am not sure the current tests would catch compressor/decompressor "corruption" resulting from the changes in this PR, at least i wasn't able to run into such case so far).
Running the available speed.js points towards a very minor throughput improvement for the 5000 roundtrips part of the test.
Each benchmark run fluctuates quite a bit on my work machine so i opted for not posting the results here.
I am attaching a set of graphs showing the performance
server_no_context_takeover
was introduced on the server sideNode version: LTS v14.15.4
Platform: Linux x64