Skip to content

Commit b434b9f

Browse files
committed
[fix] Fix close edge cases
Ensure that `socket.end()` is called if an error occurs simultaneously on both peers. Refs: #1902
1 parent c3fdc99 commit b434b9f

File tree

3 files changed

+150
-10
lines changed

3 files changed

+150
-10
lines changed

lib/websocket.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,13 @@ class WebSocket extends EventEmitter {
225225
}
226226

227227
if (this.readyState === WebSocket.CLOSING) {
228-
if (this._closeFrameSent && this._closeFrameReceived) this._socket.end();
228+
if (
229+
this._closeFrameSent &&
230+
(this._closeFrameReceived || this._receiver._writableState.errorEmitted)
231+
) {
232+
this._socket.end();
233+
}
234+
229235
return;
230236
}
231237

@@ -238,7 +244,13 @@ class WebSocket extends EventEmitter {
238244
if (err) return;
239245

240246
this._closeFrameSent = true;
241-
if (this._closeFrameReceived) this._socket.end();
247+
248+
if (
249+
this._closeFrameReceived ||
250+
this._receiver._writableState.errorEmitted
251+
) {
252+
this._socket.end();
253+
}
242254
});
243255

244256
//

test/create-websocket-stream.test.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ describe('createWebSocketStream', () => {
204204

205205
it('reemits errors', (done) => {
206206
let duplexCloseEventEmitted = false;
207+
let serverClientCloseEventEmitted = false;
208+
207209
const wss = new WebSocket.Server({ port: 0 }, () => {
208210
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
209211
const duplex = createWebSocketStream(ws);
@@ -218,17 +220,19 @@ describe('createWebSocketStream', () => {
218220

219221
duplex.on('close', () => {
220222
duplexCloseEventEmitted = true;
223+
if (serverClientCloseEventEmitted) wss.close(done);
221224
});
222225
});
223226
});
224227

225228
wss.on('connection', (ws) => {
226229
ws._socket.write(Buffer.from([0x85, 0x00]));
227230
ws.on('close', (code, reason) => {
228-
assert.ok(duplexCloseEventEmitted);
229231
assert.strictEqual(code, 1002);
230232
assert.strictEqual(reason, '');
231-
wss.close(done);
233+
234+
serverClientCloseEventEmitted = true;
235+
if (duplexCloseEventEmitted) wss.close(done);
232236
});
233237
});
234238
});

test/websocket.test.js

+130-6
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,8 @@ describe('WebSocket', () => {
430430
describe('Events', () => {
431431
it("emits an 'error' event if an error occurs", (done) => {
432432
let clientCloseEventEmitted = false;
433+
let serverClientCloseEventEmitted = false;
434+
433435
const wss = new WebSocket.Server({ port: 0 }, () => {
434436
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
435437

@@ -442,19 +444,22 @@ describe('WebSocket', () => {
442444
);
443445

444446
ws.on('close', (code, reason) => {
445-
clientCloseEventEmitted = true;
446447
assert.strictEqual(code, 1006);
447448
assert.strictEqual(reason, '');
449+
450+
clientCloseEventEmitted = true;
451+
if (serverClientCloseEventEmitted) wss.close(done);
448452
});
449453
});
450454
});
451455

452456
wss.on('connection', (ws) => {
453457
ws.on('close', (code, reason) => {
454-
assert.ok(clientCloseEventEmitted);
455458
assert.strictEqual(code, 1002);
456459
assert.strictEqual(reason, '');
457-
wss.close(done);
460+
461+
serverClientCloseEventEmitted = true;
462+
if (clientCloseEventEmitted) wss.close(done);
458463
});
459464

460465
ws._socket.write(Buffer.from([0x85, 0x00]));
@@ -1419,16 +1424,19 @@ describe('WebSocket', () => {
14191424
});
14201425

14211426
it('honors the `mask` option', (done) => {
1427+
let clientCloseEventEmitted = false;
14221428
let serverClientCloseEventEmitted = false;
1429+
14231430
const wss = new WebSocket.Server({ port: 0 }, () => {
14241431
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
14251432

14261433
ws.on('open', () => ws.send('hi', { mask: false }));
14271434
ws.on('close', (code, reason) => {
1428-
assert.ok(serverClientCloseEventEmitted);
14291435
assert.strictEqual(code, 1002);
14301436
assert.strictEqual(reason, '');
1431-
wss.close(done);
1437+
1438+
clientCloseEventEmitted = true;
1439+
if (serverClientCloseEventEmitted) wss.close(done);
14321440
});
14331441
});
14341442

@@ -1450,9 +1458,11 @@ describe('WebSocket', () => {
14501458
);
14511459

14521460
ws.on('close', (code, reason) => {
1453-
serverClientCloseEventEmitted = true;
14541461
assert.strictEqual(code, 1006);
14551462
assert.strictEqual(reason, '');
1463+
1464+
serverClientCloseEventEmitted = true;
1465+
if (clientCloseEventEmitted) wss.close(done);
14561466
});
14571467
});
14581468
});
@@ -2760,4 +2770,118 @@ describe('WebSocket', () => {
27602770
});
27612771
});
27622772
});
2773+
2774+
describe('Connection close edge cases', () => {
2775+
it('closes cleanly after simultaneous errors (1/2)', (done) => {
2776+
let clientCloseEventEmitted = false;
2777+
let serverClientCloseEventEmitted = false;
2778+
2779+
const wss = new WebSocket.Server({ port: 0 }, () => {
2780+
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
2781+
2782+
ws.on('error', (err) => {
2783+
assert.ok(err instanceof RangeError);
2784+
assert.strictEqual(err.code, 'WS_ERR_INVALID_OPCODE');
2785+
assert.strictEqual(
2786+
err.message,
2787+
'Invalid WebSocket frame: invalid opcode 5'
2788+
);
2789+
2790+
ws.on('close', (code, reason) => {
2791+
assert.strictEqual(code, 1006);
2792+
assert.strictEqual(reason, '');
2793+
2794+
clientCloseEventEmitted = true;
2795+
if (serverClientCloseEventEmitted) wss.close(done);
2796+
});
2797+
});
2798+
2799+
ws.on('open', () => {
2800+
// Write an invalid frame in both directions to trigger simultaneous
2801+
// failure.
2802+
const chunk = Buffer.from([0x85, 0x00]);
2803+
2804+
wss.clients.values().next().value._socket.write(chunk);
2805+
ws._socket.write(chunk);
2806+
});
2807+
});
2808+
2809+
wss.on('connection', (ws) => {
2810+
ws.on('error', (err) => {
2811+
assert.ok(err instanceof RangeError);
2812+
assert.strictEqual(err.code, 'WS_ERR_INVALID_OPCODE');
2813+
assert.strictEqual(
2814+
err.message,
2815+
'Invalid WebSocket frame: invalid opcode 5'
2816+
);
2817+
2818+
ws.on('close', (code, reason) => {
2819+
assert.strictEqual(code, 1006);
2820+
assert.strictEqual(reason, '');
2821+
2822+
serverClientCloseEventEmitted = true;
2823+
if (clientCloseEventEmitted) wss.close(done);
2824+
});
2825+
});
2826+
});
2827+
});
2828+
2829+
it('closes cleanly after simultaneous errors (2/2)', (done) => {
2830+
let clientCloseEventEmitted = false;
2831+
let serverClientCloseEventEmitted = false;
2832+
2833+
const wss = new WebSocket.Server({ port: 0 }, () => {
2834+
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
2835+
2836+
ws.on('error', (err) => {
2837+
assert.ok(err instanceof RangeError);
2838+
assert.strictEqual(err.code, 'WS_ERR_INVALID_OPCODE');
2839+
assert.strictEqual(
2840+
err.message,
2841+
'Invalid WebSocket frame: invalid opcode 5'
2842+
);
2843+
2844+
ws.on('close', (code, reason) => {
2845+
assert.strictEqual(code, 1006);
2846+
assert.strictEqual(reason, '');
2847+
2848+
clientCloseEventEmitted = true;
2849+
if (serverClientCloseEventEmitted) wss.close(done);
2850+
});
2851+
});
2852+
2853+
ws.on('open', () => {
2854+
// Write an invalid frame in both directions and change the
2855+
// `readyState` to `WebSocket.CLOSING`.
2856+
const chunk = Buffer.from([0x85, 0x00]);
2857+
const serverWs = wss.clients.values().next().value;
2858+
2859+
serverWs._socket.write(chunk);
2860+
serverWs.close();
2861+
2862+
ws._socket.write(chunk);
2863+
ws.close();
2864+
});
2865+
});
2866+
2867+
wss.on('connection', (ws) => {
2868+
ws.on('error', (err) => {
2869+
assert.ok(err instanceof RangeError);
2870+
assert.strictEqual(err.code, 'WS_ERR_INVALID_OPCODE');
2871+
assert.strictEqual(
2872+
err.message,
2873+
'Invalid WebSocket frame: invalid opcode 5'
2874+
);
2875+
2876+
ws.on('close', (code, reason) => {
2877+
assert.strictEqual(code, 1006);
2878+
assert.strictEqual(reason, '');
2879+
2880+
serverClientCloseEventEmitted = true;
2881+
if (clientCloseEventEmitted) wss.close(done);
2882+
});
2883+
});
2884+
});
2885+
});
2886+
});
27632887
});

0 commit comments

Comments
 (0)