Skip to content

Commit aa1dcd5

Browse files
committed
[fix] Make WebSocket#close() set the close timer immediately
If there is buffered data to write and the stream is not flowing, it is possible that the callback of `Sender.prototype.close()` is never called.
1 parent 297f56d commit aa1dcd5

File tree

2 files changed

+44
-37
lines changed

2 files changed

+44
-37
lines changed

lib/websocket.js

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const {
2424

2525
const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
2626
const protocolVersions = [8, 13];
27-
const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
27+
const closeTimeout = 30 * 1000;
2828

2929
/**
3030
* Class representing a WebSocket.
@@ -87,8 +87,9 @@ class WebSocket extends EventEmitter {
8787
}
8888

8989
/**
90-
* This deviates from the WHATWG interface since ws doesn't support the required
91-
* default "blob" type (instead we define a custom "nodebuffer" type).
90+
* This deviates from the WHATWG interface since ws doesn't support the
91+
* required default "blob" type (instead we define a custom "nodebuffer"
92+
* type).
9293
*
9394
* @type {String}
9495
*/
@@ -230,20 +231,16 @@ class WebSocket extends EventEmitter {
230231
if (err) return;
231232

232233
this._closeFrameSent = true;
233-
234-
if (this._socket.writable) {
235-
if (this._closeFrameReceived) this._socket.end();
236-
237-
//
238-
// Ensure that the connection is closed even if the closing handshake
239-
// fails.
240-
//
241-
this._closeTimer = setTimeout(
242-
this._socket.destroy.bind(this._socket),
243-
closeTimeout
244-
);
245-
}
234+
if (this._closeFrameReceived) this._socket.end();
246235
});
236+
237+
//
238+
// Specify a timeout for the closing handshake to complete.
239+
//
240+
this._closeTimer = setTimeout(
241+
this._socket.destroy.bind(this._socket),
242+
closeTimeout
243+
);
247244
}
248245

249246
/**

test/websocket.test.js

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,27 +1305,6 @@ describe('WebSocket', () => {
13051305
});
13061306
});
13071307

1308-
it('ends connection to the server', (done) => {
1309-
const wss = new WebSocket.Server(
1310-
{
1311-
clientTracking: false,
1312-
port: 0
1313-
},
1314-
() => {
1315-
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
1316-
1317-
ws.on('open', () => {
1318-
ws.on('close', (code, reason) => {
1319-
assert.strictEqual(reason, 'some reason');
1320-
assert.strictEqual(code, 1000);
1321-
wss.close(done);
1322-
});
1323-
ws.close(1000, 'some reason');
1324-
});
1325-
}
1326-
);
1327-
});
1328-
13291308
it('permits all buffered data to be delivered', (done) => {
13301309
const wss = new WebSocket.Server(
13311310
{
@@ -1383,6 +1362,37 @@ describe('WebSocket', () => {
13831362

13841363
wss.on('connection', (ws) => ws.close());
13851364
});
1365+
1366+
it('sets a timer for the closing handshake to complete', (done) => {
1367+
const wss = new WebSocket.Server({ port: 0 }, () => {
1368+
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
1369+
1370+
ws.on('close', (code, reason) => {
1371+
assert.strictEqual(code, 1000);
1372+
assert.strictEqual(reason, 'some reason');
1373+
wss.close(done);
1374+
});
1375+
1376+
ws.on('open', () => {
1377+
let callbackCalled = false;
1378+
1379+
assert.strictEqual(ws._closeTimer, null);
1380+
1381+
ws.send('foo', () => {
1382+
callbackCalled = true;
1383+
});
1384+
1385+
ws.close(1000, 'some reason');
1386+
1387+
//
1388+
// Check that the close timer is set even if the `Sender.close()`
1389+
// callback is not called.
1390+
//
1391+
assert.strictEqual(callbackCalled, false);
1392+
assert.strictEqual(ws._closeTimer._idleTimeout, 30000);
1393+
});
1394+
});
1395+
});
13861396
});
13871397

13881398
describe('#terminate', () => {

0 commit comments

Comments
 (0)