Skip to content

Commit b29154b

Browse files
authored
[fix] Call buffered send callbacks on abnormal closure (#1702)
If the socket is closed while data is being compressed, invoke the current send callback and the buffered send callbacks with an error before emitting the `'close'` event. Refs: nodejs/node#30596
1 parent 3e7f69c commit b29154b

5 files changed

+94
-21
lines changed

lib/permessage-deflate.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,18 @@ class PerMessageDeflate {
131131
}
132132

133133
if (this._deflate) {
134-
if (this._deflate[kCallback]) {
135-
this._deflate[kCallback]();
136-
}
134+
const callback = this._deflate[kCallback];
137135

138136
this._deflate.close();
139137
this._deflate = null;
138+
139+
if (callback) {
140+
callback(
141+
new Error(
142+
'The deflate stream was closed while data was being processed'
143+
)
144+
);
145+
}
140146
}
141147
}
142148

@@ -314,9 +320,7 @@ class PerMessageDeflate {
314320
zlibLimiter.add((done) => {
315321
this._compress(data, fin, (err, result) => {
316322
done();
317-
if (err || result) {
318-
callback(err, result);
319-
}
323+
callback(err, result);
320324
});
321325
});
322326
}

lib/sender.js

+16
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,22 @@ class Sender {
306306

307307
this._deflating = true;
308308
perMessageDeflate.compress(data, options.fin, (_, buf) => {
309+
if (this._socket.destroyed) {
310+
const err = new Error(
311+
'The socket was closed while data was being compressed'
312+
);
313+
314+
if (typeof cb === 'function') cb(err);
315+
316+
for (let i = 0; i < this._queue.length; i++) {
317+
const callback = this._queue[i][4];
318+
319+
if (typeof callback === 'function') callback(err);
320+
}
321+
322+
return;
323+
}
324+
309325
this._deflating = false;
310326
options.readOnly = false;
311327
this.sendFrame(Sender.frame(buf, options), cb);

lib/websocket.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,8 @@ class WebSocket extends EventEmitter {
179179
* @private
180180
*/
181181
emitClose() {
182-
this.readyState = WebSocket.CLOSED;
183-
184182
if (!this._socket) {
183+
this.readyState = WebSocket.CLOSED;
185184
this.emit('close', this._closeCode, this._closeMessage);
186185
return;
187186
}
@@ -191,6 +190,7 @@ class WebSocket extends EventEmitter {
191190
}
192191

193192
this._receiver.removeAllListeners();
193+
this.readyState = WebSocket.CLOSED;
194194
this.emit('close', this._closeCode, this._closeMessage);
195195
}
196196

test/permessage-deflate.test.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -615,15 +615,19 @@ describe('PerMessageDeflate', () => {
615615
});
616616
});
617617

618-
it("doesn't call the callback if the deflate stream is closed prematurely", (done) => {
618+
it('calls the callback if the deflate stream is closed prematurely', (done) => {
619619
const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
620620
const buf = Buffer.from('A'.repeat(50));
621621

622622
perMessageDeflate.accept([{}]);
623-
perMessageDeflate.compress(buf, true, () => {
624-
done(new Error('Unexpected callback invocation'));
623+
perMessageDeflate.compress(buf, true, (err) => {
624+
assert.ok(err instanceof Error);
625+
assert.strictEqual(
626+
err.message,
627+
'The deflate stream was closed while data was being processed'
628+
);
629+
done();
625630
});
626-
perMessageDeflate._deflate.on('close', done);
627631

628632
process.nextTick(() => perMessageDeflate.cleanup());
629633
});

test/websocket.test.js

+58-9
Original file line numberDiff line numberDiff line change
@@ -2341,6 +2341,52 @@ describe('WebSocket', () => {
23412341
ws.on('message', (message) => ws.send(message, { compress: true }));
23422342
});
23432343
});
2344+
2345+
it('calls the callback if the socket is closed prematurely', (done) => {
2346+
const wss = new WebSocket.Server(
2347+
{ perMessageDeflate: true, port: 0 },
2348+
() => {
2349+
const called = [];
2350+
const ws = new WebSocket(`ws://localhost:${wss.address().port}`, {
2351+
perMessageDeflate: { threshold: 0 }
2352+
});
2353+
2354+
ws.on('open', () => {
2355+
ws.send('foo');
2356+
ws.send('bar', (err) => {
2357+
called.push(1);
2358+
2359+
assert.strictEqual(ws.readyState, WebSocket.CLOSING);
2360+
assert.ok(err instanceof Error);
2361+
assert.strictEqual(
2362+
err.message,
2363+
'The socket was closed while data was being compressed'
2364+
);
2365+
});
2366+
ws.send('baz');
2367+
ws.send('qux', (err) => {
2368+
called.push(2);
2369+
2370+
assert.strictEqual(ws.readyState, WebSocket.CLOSING);
2371+
assert.ok(err instanceof Error);
2372+
assert.strictEqual(
2373+
err.message,
2374+
'The socket was closed while data was being compressed'
2375+
);
2376+
});
2377+
});
2378+
2379+
ws.on('close', () => {
2380+
assert.deepStrictEqual(called, [1, 2]);
2381+
wss.close(done);
2382+
});
2383+
}
2384+
);
2385+
2386+
wss.on('connection', (ws) => {
2387+
ws._socket.end();
2388+
});
2389+
});
23442390
});
23452391

23462392
describe('#terminate', () => {
@@ -2356,19 +2402,22 @@ describe('WebSocket', () => {
23562402
});
23572403

23582404
ws.on('open', () => {
2359-
ws.send('hi', () =>
2360-
done(new Error('Unexpected callback invocation'))
2361-
);
2405+
ws.send('hi', (err) => {
2406+
assert.strictEqual(ws.readyState, WebSocket.CLOSING);
2407+
assert.ok(err instanceof Error);
2408+
assert.strictEqual(
2409+
err.message,
2410+
'The socket was closed while data was being compressed'
2411+
);
2412+
2413+
ws.on('close', () => {
2414+
wss.close(done);
2415+
});
2416+
});
23622417
ws.terminate();
23632418
});
23642419
}
23652420
);
2366-
2367-
wss.on('connection', (ws) => {
2368-
ws.on('close', () => {
2369-
wss.close(done);
2370-
});
2371-
});
23722421
});
23732422

23742423
it('can be used while data is being decompressed', (done) => {

0 commit comments

Comments
 (0)