-
-
Notifications
You must be signed in to change notification settings - Fork 32.7k
Closed
Labels
http2Issues or PRs related to the http2 subsystem.Issues or PRs related to the http2 subsystem.
Description
- Version:
v14.14.0 + v12.19.0 - Platform:
Win10(2004, 64bit) + Ubuntu 18.04.4(wsl2, Linux 4.19.128-microsoft-standard SMP Tue Jun 23 12:58:10 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux) - Subsystem:
http2
What steps will reproduce the bug?
server.js
const http2 = require('http2')
const fs = require('fs')
const childProcess = require('child_process')
const PORT = 8443
module.exports = main()
async function main() {
if (!fs.existsSync('server.key')) {
childProcess.execSync(`openssl req -subj '/CN=localhost/O=localhost/C=US' -nodes -new -x509 -keyout server.key -out server.cert`)
}
const options = {
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.cert')
}
return new Promise((resolve, reject) => {
const server = http2.createSecureServer(options)
server.on('stream', (stream) => {
stream.on('error', error => {console.log('server:', 'stream', error)})
// stream.respond({
// 'content-type': 'text/html; charset=utf-8',
// ':status': 200
// })
// stream.end('<h1>Hello World</h1>')
})
server.listen(PORT, () => {
console.log('server:', `https://localhost:${PORT}`)
resolve(server)
})
server.on('error', reject)
let sockets = []
server.on('connection', socket => {
console.log('server:', 'new client', socket.address())
socket.setNoDelay()
sockets.push(socket)
})
server.kill = () => {
sockets.forEach(socket => socket.destroy())
server.close()
}
})
}
client.js
const childProcess = require('child_process')
const http2 = require('http2')
const net = require('net')
const assert = require('assert')
const {Duplex} = require('stream')
const ARGV = process.argv.slice(-1)[0]
console.log({ARGV})
main()
async function main() {
const url = 'https://localhost:8443'
// {
// // use socket
// const server = await makeServer()
// const socket = net.connect({host: 'localhost', port: '8443'}, async () => {
// socket.on('error', error => socket.destroy(error))
// await makeRequest(url, socket, server)
// server.kill()
// })
// }
{
// use duplex
const server = await makeServer()
const socket = net.connect({host: 'localhost', port: '8443'}, async () => {
const wrapSocket = new WrapSocket(socket)
await makeRequest(url, wrapSocket, server)
server.kill()
})
}
}
async function makeServer() {
let server
if (ARGV.includes('spawn')) {
server = childProcess.spawn('node', ['server.js'], {stdio: 'inherit'})
await sleep(500)
} else {
server = await require('./server')
}
process.on('uncaughtException', error => {
console.log('client:', 'uncaughtException!!!\n', error)
server.kill()
})
return server
}
async function makeRequest(url, socket, server) {
const h2Session = http2.connect(url, {
rejectUnauthorized: false,
socket
})
h2Session.on('error', error => {console.log('client:', 'h2Session error', error.message)})
const stream = h2Session.request({[http2.constants.HTTP2_HEADER_PATH]: '/'})
stream.on('error', error => {console.log('client:', 'stream error', error.message)})
stream.on('response', headers => {});
if (ARGV.includes('patch')) {
patch(h2Session, socket)
}
await sleep(500)
// abort the request when waiting the server response
if (ARGV.includes('remote')) {
server.kill() // by remote side socket
} else {
socket.destroy() // by self(local side socket)
}
await sleep(500)
// should?: destroyed === true
console.log('client:', 'h2Session.destroyed', h2Session.destroyed)
// should?: h2Session.socket === undefined or destroyed === true
console.log('client:', 'h2Session.socket.destroyed', h2Session.socket && h2Session.socket.destroyed)
// should: destroyed === true
console.log('client:', 'socket.destroyed', socket.destroyed)
h2Session.close(() => { // <-- crash
console.log('client:', 'h2Session.close')
})
await sleep(200)
console.log('client:', 'all errors caught')
}
function patch(h2Session, socket) {
// require --expose-internals
const {kSocket} = require('internal/http2/util')
h2Session[kSocket]._handle._parentWrap.on('close', ()=>{
h2Session[kSocket] && h2Session[kSocket].destroy()
})
}
class WrapSocket extends Duplex {
constructor(socket) {
super({autoDestroy: true, allowHalfOpen: false})
socket.on('end', data => this.push(null))
socket.on('close', () => this.destroy())
socket.on('error', error => this.destroy(error))
socket.on('data', data => this.push(data))
this.socket = socket
}
_write(data, encoding, callback) {
this.socket.write(data, encoding, callback)
}
_final(callback) {
callback()
}
_read(size) {
// this.socket.on('data', data => this.push(data))
}
_destroy(error, callback) {
callback(error)
}
}
async function sleep(ms) {
return new Promise(resolve => {setTimeout(() => resolve(), ms)})
}
the error(bug):
// only can be caught by process.on('uncaughtException')
TypeError: Cannot read property 'finishWrite' of null
at JSStreamSocket.finishWrite (internal/js_stream_socket.js:210:12)
at Immediate.<anonymous> (internal/js_stream_socket.js:195:14)
at processImmediate (internal/timers.js:461:21)
client.js argv(command line options) mean:
local, remote: close the socket by local side(client), or close the socket by remote side(server).
spawn: spawn the server.js in new process, or not.
patch: undo the change introduced by https://github.com/nodejs/node/pull/34105, or not.
Win10(2004)
> node -v
v14.14.0 + v12.19.0
> node client.js local
(error)
> node client.js local+spawn
(error)
> node client.js remote
(no error)
> node client.js remote+spawn
(error, v14.14.0)
(no error, v12.19.0)
> node --expose-internals client.js local+patch
(no error)
> node --expose-internals client.js remote+spawn+patch
(error, v14.14.0(patch not work!))
> node -v
v12.18.3
> node client.js local
(...and all combinations)
(no error)
Ubuntu 18.04.4(wsl2)
> node -v
v14.14.0 + v12.19.0
> node client.js local
(error)
> node client.js local+spawn
(error)
> node client.js remote
(no error)
> node client.js remote+spawn
(no error)
> node --expose-internals client.js local+patch
(no error)
> node -v
v12.18.3
> node client.js local
(...and all combinations)
(no error)
How often does it reproduce? Is there a required condition?
What is the expected behavior?
No "TypeError: Cannot read property 'finishShutdown' of null" error
What do you see instead?
Additional information
Metadata
Metadata
Assignees
Labels
http2Issues or PRs related to the http2 subsystem.Issues or PRs related to the http2 subsystem.