Skip to content

Commit

Permalink
net: wait for shutdown to complete before closing
Browse files Browse the repository at this point in the history
When not allowing half open, handle.close would be
invoked before shutdown has been called and
completed causing a potential data race.

Fixes: #32486 (comment)

PR-URL: #32491
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
  • Loading branch information
ronag authored and targos committed Apr 22, 2020
1 parent 959055f commit ed21d32
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 6 deletions.
10 changes: 10 additions & 0 deletions lib/internal/stream_base_commons.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,16 @@ function onStreamRead(arrayBuffer) {
if (stream[kMaybeDestroy])
stream.on('end', stream[kMaybeDestroy]);

// TODO(ronag): Without this `readStop`, `onStreamRead`
// will be called once more (i.e. after Readable.ended)
// on Windows causing a ECONNRESET, failing the
// test-https-truncate test.
if (handle.readStop) {
const err = handle.readStop();
if (err)
return stream.destroy(errnoException(err, 'read'));
}

// Push a null to signal the end of data.
// Do it before `maybeDestroy` for correct order of events:
// `end` -> `close`
Expand Down
6 changes: 3 additions & 3 deletions lib/net.js
Original file line number Diff line number Diff line change
Expand Up @@ -629,9 +629,9 @@ function onReadableStreamEnd() {
this.write = writeAfterFIN;
if (this.writable)
this.end();
}

if (!this.destroyed && !this.writable && !this.writableLength)
else if (!this.writableLength)
this.destroy();
} else if (!this.destroyed && !this.writable && !this.writableLength)
this.destroy();
}

Expand Down
4 changes: 1 addition & 3 deletions test/async-hooks/test-graph.tls-write.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ function onexit() {
{ type: 'TCPCONNECTWRAP',
id: 'tcpconnect:1', triggerAsyncId: 'tcp:1' },
{ type: 'TCPWRAP', id: 'tcp:2', triggerAsyncId: 'tcpserver:1' },
{ type: 'TLSWRAP', id: 'tls:2', triggerAsyncId: 'tcpserver:1' },
{ type: 'Immediate', id: 'immediate:1', triggerAsyncId: 'tcp:2' },
{ type: 'Immediate', id: 'immediate:2', triggerAsyncId: 'tcp:1' },
{ type: 'TLSWRAP', id: 'tls:2', triggerAsyncId: 'tcpserver:1' }
]
);
}
25 changes: 25 additions & 0 deletions test/parallel/test-net-allow-half-open.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const net = require('net');

const server = net.createServer(common.mustCall((socket) => {
socket.end(Buffer.alloc(1024));
})).listen(0, common.mustCall(() => {
const socket = net.connect(server.address().port);
assert.strictEqual(socket.allowHalfOpen, false);
socket.resume();
socket.on('end', common.mustCall(() => {
process.nextTick(() => {
// Ensure socket is not destroyed straight away
// without proper shutdown.
assert(!socket.destroyed);
server.close();
});
}));
socket.on('finish', common.mustCall(() => {
assert(!socket.destroyed);
}));
socket.on('close', common.mustCall());
}));

0 comments on commit ed21d32

Please sign in to comment.