-
-
Notifications
You must be signed in to change notification settings - Fork 33.5k
Description
Version
v18.16.1
Platform
Darwin Kernel Version 19.6.0
Subsystem
none
What steps will reproduce the bug?
I can reliably reproduce a Segmentation fault
by using the core http2
module.
Step 1: Acquire a TLS/SSL public+private key pair
I followed this guide in the Node.js documentation to create a self-signed certificate.
Step 2: Run this script to start an HTTP/2 server:
'use strict';
const fs = require('fs');
const http2 = require('http2');
(async function main() {
const sockets = new Set();
const sessions = new Set();
const server = http2.createSecureServer({
cert: fs.readFileSync('test-cert.pem'),
key: fs.readFileSync('test-key.pem'),
});
server.on('request', (req, res) => {
const message = 'hello world.';
res.setHeader('Content-Length', String(Buffer.byteLength(message)));
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.writeHead(200);
res.end(message);
});
server.on('connection', (socket) => {
sockets.add(socket);
socket.on('close', () => sockets.delete(socket));
socket.setTimeout(2000, () => {
console.log('socket timeout');
socket.resetAndDestroy();
});
});
server.on('session', (session) => {
sessions.add(session);
session.on('close', () => sessions.delete(session));
});
async function gracefullyClose() {
for (const session of sessions) {
session.close();
}
await new Promise(resolve => server.close(resolve));
}
await new Promise(resolve => server.listen(443, resolve));
console.log('Listening at https://localhost:443');
process.once('SIGINT', () => {
console.log('closing...');
gracefullyClose().then(() => console.log('done'));
});
})();
Step 3:
Open up Google Chrome (or probably any browser or HTTP client) and open the URL: https://localhost:443
. You should get a hello world.
plain text response. The only purpose of this step is to open an HTTP/2 session on the server. You don't really need Google Chrome specifically.
Step 4:
Wait for the Node.js server to print socket timeout
in the console. This means the there was no activity on the socket for 2 seconds and so the socket was destroyed.
Step 5:
Close the Node.js server by triggering a SIGINT (i.e., press ctrl+c
in the console). This will initiate a graceful shutdown of the server, which involves calling session.close()
on every open session. This will cause the program to crash and print Segmentation fault: 11
.
How often does it reproduce? Is there a required condition?
I can reliably reproduce it 100% of the time.
What is the expected behavior? Why is that the expected behavior?
The expected behavior is for the session.close()
to not cause a segfault. Additionally, when the socket times out and socket.resetAndDestroy()
is called, the session's close
event should be emitted (which is currently not happening). The current behavior contradicts the Node.js docs:
When either the Socket or the Http2Session are destroyed, both will be destroyed.
What do you see instead?
Segmentation fault: 11
Additional information
I know some people might suggest that I call setTimeout
on the session instead of the socket, and that I use session.destroy()
instead of socket.resetAndDestroy()
. However, that isn't really an acceptable solution, because I need to close ALL connections that are established, even if an HTTP/2 session hasn't yet been established on that connection.