- 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
consthttp2=require('http2')constfs=require('fs')constchildProcess=require('child_process')constPORT=8443module.exports=main()asyncfunctionmain(){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`)}constoptions={key: fs.readFileSync('server.key'),cert: fs.readFileSync('server.cert')}returnnewPromise((resolve,reject)=>{constserver=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)letsockets=[]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
constchildProcess=require('child_process')consthttp2=require('http2')constnet=require('net')constassert=require('assert')const{Duplex}=require('stream')constARGV=process.argv.slice(-1)[0]console.log({ARGV})main()asyncfunctionmain(){consturl='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 duplexconstserver=awaitmakeServer()constsocket=net.connect({host: 'localhost',port: '8443'},async()=>{constwrapSocket=newWrapSocket(socket)awaitmakeRequest(url,wrapSocket,server)server.kill()})}}asyncfunctionmakeServer(){letserverif(ARGV.includes('spawn')){server=childProcess.spawn('node',['server.js'],{stdio: 'inherit'})awaitsleep(500)}else{server=awaitrequire('./server')}process.on('uncaughtException',error=>{console.log('client:','uncaughtException!!!\n',error)server.kill()})returnserver}asyncfunctionmakeRequest(url,socket,server){consth2Session=http2.connect(url,{rejectUnauthorized: false,
socket
})h2Session.on('error',error=>{console.log('client:','h2Session error',error.message)})conststream=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)}awaitsleep(500)// abort the request when waiting the server responseif(ARGV.includes('remote')){server.kill()// by remote side socket}else{socket.destroy()// by self(local side socket)}awaitsleep(500)// should?: destroyed === trueconsole.log('client:','h2Session.destroyed',h2Session.destroyed)// should?: h2Session.socket === undefined or destroyed === trueconsole.log('client:','h2Session.socket.destroyed',h2Session.socket&&h2Session.socket.destroyed)// should: destroyed === trueconsole.log('client:','socket.destroyed',socket.destroyed)h2Session.close(()=>{// <-- crashconsole.log('client:','h2Session.close')})awaitsleep(200)console.log('client:','all errors caught')}functionpatch(h2Session,socket){// require --expose-internalsconst{kSocket}=require('internal/http2/util')h2Session[kSocket]._handle._parentWrap.on('close',()=>{h2Session[kSocket]&&h2Session[kSocket].destroy()})}classWrapSocketextendsDuplex{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)}}asyncfunctionsleep(ms){returnnewPromise(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
v14.14.0 + v12.19.0
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)
http2
What steps will reproduce the bug?
server.js
client.js
the error(bug):
client.js argv(command line options) mean:
Win10(2004)
Ubuntu 18.04.4(wsl2)
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