Skip to content

Http2: Cannot read property 'finishWrite' of null #35695

Description

@panmenghan
  • 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    http2Issues or PRs related to the http2 subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions