Skip to content

Commit c16f526

Browse files
committed
http2: invoke pending write callback on stream destroy
When an HTTP/2 stream is destroyed while a write is in progress, the pending write callback may never be called if the write data has already been consumed by nghttp2 and moved to the session's outgoing_buffers_. In this case, the C++ side's SetImmediate cleanup finds nothing in the stream's write queue, and the callback depends on session socket write completion — which may never happen during shutdown. This leaves the Writable stream's internal state stuck, preventing cleanup of buffered writes and keeping references alive that block event loop exit. Track the pending write callback on the stream and invoke it in _destroy() before calling handle.destroy(), ensuring the Writable stream can clean up properly. The callback is made idempotent so duplicate invocations from the C++ side are harmless no-ops. Fixes: #58252 Refs: #58253
1 parent 95245a7 commit c16f526

File tree

1 file changed

+10
-0
lines changed

1 file changed

+10
-0
lines changed

lib/internal/http2/core.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ const kOptions = Symbol('options');
253253
const kOwner = owner_symbol;
254254
const kOrigin = Symbol('origin');
255255
const kPendingRequestCalls = Symbol('kPendingRequestCalls');
256+
const kPendingWriteCb = Symbol('kPendingWriteCb');
256257
const kProceed = Symbol('proceed');
257258
const kRemoteSettings = Symbol('remote-settings');
258259
const kRequestAsyncResource = Symbol('requestAsyncResource');
@@ -2276,10 +2277,13 @@ class Http2Stream extends Duplex {
22762277
cb(err);
22772278
};
22782279
const writeCallback = (err) => {
2280+
if (!waitingForWriteCallback) return;
2281+
this[kPendingWriteCb] = null;
22792282
waitingForWriteCallback = false;
22802283
writeCallbackErr = err;
22812284
done();
22822285
};
2286+
this[kPendingWriteCb] = writeCallback;
22832287
const endCheckCallback = (err) => {
22842288
waitingForEndCheck = false;
22852289
endCheckCallbackErr = err;
@@ -2445,6 +2449,12 @@ class Http2Stream extends Duplex {
24452449
closeStream(this, code, hasHandle ? kForceRstStream : kNoRstStream);
24462450
this.push(null);
24472451

2452+
const pendingWriteCb = this[kPendingWriteCb];
2453+
if (pendingWriteCb) {
2454+
this[kPendingWriteCb] = null;
2455+
pendingWriteCb(err);
2456+
}
2457+
24482458
if (hasHandle) {
24492459
handle.destroy();
24502460
sessionState.streams.delete(id);

0 commit comments

Comments
 (0)