Repository navigation
Http2: Cannot read property 'finishWrite' of null #35695
Description
Activity
- addedhttp2Issues and PRs related to the http2 subsystem.Issues and PRs related to the http2 subsystem.
on Oct 18, 2020 This is a collection of various minor issues all linked to the edge case of the underlying stream of the TLS listener being null after being indirectly removed by the
socket.destroy(). When passing throughJSStreamSocket(because of theWrapSocketclass), it throws an exception, however when interfaced to the C++ stream, it ends with a segfault. The example is overly complex, you just need to destroy the underlying socket of an open TLS connection and you will run into problems. @mildsunrise
TLSWrap is full ofif (stream_ != nullptr)and a couple of them are missing -TLSWrap::EncOut()is one exampleThe segfault does not occur with the new crypto_tls code, it occurs only on v14.x
The TypeError is still there.@mmomtchev
I first post the issue to there.
Reproduce the error against https://www.example.com. (but can not reproduce the error every time, on win10)
Now I also test with node v15.0.0, but the error is still there(win10).I was able to compress it down to this unit test:
'use strict'; const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); const h2 = require('http2'); const net = require('net'); const fixtures = require('../common/fixtures'); const { Duplex } = require('stream'); const server = h2.createSecureServer({ key: fixtures.readKey('agent1-key.pem'), cert: fixtures.readKey('agent1-cert.pem') }); class JSSocket extends Duplex { constructor(socket) { super(); socket.on('close', () => this.destroy()); socket.on('data', (data) => this.push(data)); this.socket = socket; } _write(data, encoding, callback) { this.socket.write(data, encoding, callback); } _read(size) { } } server.listen(0, common.mustCall(function() { const socket = net.connect({ host: 'localhost', port: this.address().port }, () => { const client = h2.connect(`https://localhost:${this.address().port}`, { rejectUnauthorized: false, socket: new JSSocket(socket) }); const req = client.request(); setTimeout(() => socket.destroy(), 500); setTimeout(() => client.close(), 1000); setTimeout(() => server.close(), 1500); req.on('close', common.mustCall(() => { })); }); }));
With JSSocket - exception on Node 14.x and master
Without JSSocket (passsocketinstead ofnew JSSocket(socket)) - segfault on Node 14.x and ok since the crypto_tls merge on master
The final'close'event is never calledThe reason I try to spawn the server in a new process and kill the server is I want server send RST packet(not FIN packet) to end the connection. Maybe the error RST end the connection is not the same as FIN.
@jasnell, @addaleax is
http2.connect(url, { socket: s })a supported code path?
Because the officially documented option is a function calledcreateConnectionreturning a socket?
However should a{ socket: s }option be passed, it will get passed totls.connectwhich supports it and will use it.
I wonder if this is intended?using createConnection instead:
... const client = h2.connect(`https://localhost:${this.address().port}`, { rejectUnauthorized: false, createConnection: () => tls.connect({ socket: new JSSocket(socket), host: 'localhost', port: this.address().port, ALPNProtocols: ['h2'] }), // socket: new JSSocket(socket) }); ...the same error:
node:internal/js_stream_socket:210 handle.finishWrite(req, errCode); ^ TypeError: Cannot read property 'finishWrite' of null at JSStreamSocket.finishWrite (node:internal/js_stream_socket:210:12) at Immediate.<anonymous> (node:internal/js_stream_socket:195:14) at processImmediate (node:internal/timers:462:21)but this can prevent the error:
... createConnection: () => { const jsSocket = new JSSocket(socket) const tlsSocket = tls.connect({ socket: jsSocket, host: 'localhost', port: this.address().port, ALPNProtocols: ['h2'] }) // tlsSocket.on('close', ()=>{jsSocket.destroy()}) jsSocket.on('close', ()=>{tlsSocket.destroy()}) // <-- add this line return tlsSocket } ...@panmenghan the exception is trivial to fix on the
createConnectionpath but it is only the tip of the iceberg on the officially unsupportedsocketpath
Maybe it is time to create another issue and submit the trivial fix for the exception@mmomtchev Thanks, please fix it. I already rewrite my code to use
createConnectioninstead.
But whyhttpsnot crash, onlyhttp2crash?http2usetlsas transport should the same ashttps?@panmenghan the bug is in the http2 code
- added 2 commits that reference this issue
on Nov 8, 2020 - added 2 commits that reference this issue
on Nov 9, 2020 - added 8 commits that reference this issue
on Dec 9, 2020 The example from first comment is reproducible in NodeJS 14.16.1.
Should not it be fixed (related commits included to 14.15.2) ?- added a commit that references this issue
on Mar 21, 2023
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