Compare commits

...

1 Commits

Author SHA1 Message Date
Meghan Denny
6e3abe9a44 net: fix timing of when closeSocketHandle callback is called 2025-06-02 22:29:23 -07:00
3 changed files with 132 additions and 4 deletions

View File

@@ -1141,11 +1141,13 @@ Socket.prototype._destroy = function _destroy(err, callback) {
if (this.resetAndClosing) {
this.resetAndClosing = false;
// TODO: node calls this _handle method .reset, should we? investigate any difference
const err = this._handle.close();
setImmediate(() => {
// TODO: node passes this function as a callback to reset, should we? but our implementation is sync
setTimeout(() => {
$debug("emit close");
this.emit("close", isException);
});
}, 0);
if (err) this.emit("error", new ErrnoException(err, "reset"));
} else if (this._closeAfterHandlingError) {
// Enqueue closing the socket as a microtask, so that the socket can be
@@ -2598,7 +2600,8 @@ function closeSocketHandle(self, isException, isCleanupPending = false) {
$debug("closeSocketHandle", isException, isCleanupPending, !!self._handle);
if (self._handle) {
self._handle.close();
setImmediate(() => {
// TODO: node passes this function as a callback to close, should we? but our implementation is sync
setTimeout(() => {
$debug("emit close", isCleanupPending);
self.emit("close", isException);
if (isCleanupPending) {
@@ -2606,7 +2609,7 @@ function closeSocketHandle(self, isException, isCleanupPending = false) {
self._handle = null;
self._sockname = null;
}
});
}, 0);
}
}

View File

@@ -0,0 +1,47 @@
'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());
}));
}
{
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(() => {
assert(!socket.destroyed);
}));
socket.end('asd');
socket.on('finish', common.mustCall(() => {
assert(!socket.destroyed);
}));
socket.on('close', common.mustCall(() => {
server.close();
}));
}));
}

View File

@@ -0,0 +1,78 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
require('../common');
const assert = require('assert');
const net = require('net');
let bytesRead = 0;
let bytesWritten = 0;
let count = 0;
const tcp = net.Server(function(s) {
console.log('tcp server connection');
// trigger old mode.
s.resume();
s.on('end', function() {
bytesRead += s.bytesRead;
console.log(`tcp socket disconnect #${count}`);
});
});
tcp.listen(0, function doTest() {
console.error('listening');
const socket = net.createConnection(this.address().port);
socket.on('connect', function() {
count++;
console.error('CLIENT connect #%d', count);
socket.write('foo', function() {
console.error('CLIENT: write cb');
socket.end('bar');
});
});
socket.on('finish', function() {
bytesWritten += socket.bytesWritten;
console.error('CLIENT end event #%d', count);
});
socket.on('close', function() {
console.error('CLIENT close event #%d', count);
console.log(`Bytes read: ${bytesRead}`);
console.log(`Bytes written: ${bytesWritten}`);
if (count < 2) {
console.error('RECONNECTING');
socket.connect(tcp.address().port);
} else {
tcp.close();
}
});
});
process.on('exit', function() {
assert.strictEqual(bytesRead, 12);
assert.strictEqual(bytesWritten, 12);
});