@@ -1136,6 +1136,7 @@ class Http2Session extends EventEmitter {
1136
1136
streams : new Map ( ) ,
1137
1137
pendingStreams : new Set ( ) ,
1138
1138
pendingAck : 0 ,
1139
+ shutdownWritableCalled : false ,
1139
1140
writeQueueSize : 0 ,
1140
1141
originSet : undefined
1141
1142
} ;
@@ -1702,6 +1703,26 @@ function afterShutdown(status) {
1702
1703
stream [ kMaybeDestroy ] ( ) ;
1703
1704
}
1704
1705
1706
+ function shutdownWritable ( callback ) {
1707
+ const handle = this [ kHandle ] ;
1708
+ if ( ! handle ) return callback ( ) ;
1709
+ const state = this [ kState ] ;
1710
+ if ( state . shutdownWritableCalled ) {
1711
+ // Backport v14.x: Session required for debugging stream object
1712
+ // debugStreamObj(this, 'shutdownWritable() already called');
1713
+ return callback ( ) ;
1714
+ }
1715
+ state . shutdownWritableCalled = true ;
1716
+
1717
+ const req = new ShutdownWrap ( ) ;
1718
+ req . oncomplete = afterShutdown ;
1719
+ req . callback = callback ;
1720
+ req . handle = handle ;
1721
+ const err = handle . shutdown ( req ) ;
1722
+ if ( err === 1 ) // synchronous finish
1723
+ return afterShutdown . call ( req , 0 ) ;
1724
+ }
1725
+
1705
1726
function finishSendTrailers ( stream , headersList ) {
1706
1727
// The stream might be destroyed and in that case
1707
1728
// there is nothing to do.
@@ -1962,10 +1983,48 @@ class Http2Stream extends Duplex {
1962
1983
1963
1984
let req ;
1964
1985
1986
+ let waitingForWriteCallback = true ;
1987
+ let waitingForEndCheck = true ;
1988
+ let writeCallbackErr ;
1989
+ let endCheckCallbackErr ;
1990
+ const done = ( ) => {
1991
+ if ( waitingForEndCheck || waitingForWriteCallback ) return ;
1992
+ const err = writeCallbackErr || endCheckCallbackErr ;
1993
+ // writeGeneric does not destroy on error and
1994
+ // we cannot enable autoDestroy,
1995
+ // so make sure to destroy on error.
1996
+ if ( err ) {
1997
+ this . destroy ( err ) ;
1998
+ }
1999
+ cb ( err ) ;
2000
+ } ;
2001
+ const writeCallback = ( err ) => {
2002
+ waitingForWriteCallback = false ;
2003
+ writeCallbackErr = err ;
2004
+ done ( ) ;
2005
+ } ;
2006
+ const endCheckCallback = ( err ) => {
2007
+ waitingForEndCheck = false ;
2008
+ endCheckCallbackErr = err ;
2009
+ done ( ) ;
2010
+ } ;
2011
+ // Shutdown write stream right after last chunk is sent
2012
+ // so final DATA frame can include END_STREAM flag
2013
+ process . nextTick ( ( ) => {
2014
+ if ( writeCallbackErr ||
2015
+ ! this . _writableState . ending ||
2016
+ this . _writableState . buffered . length ||
2017
+ ( this [ kState ] . flags & STREAM_FLAGS_HAS_TRAILERS ) )
2018
+ return endCheckCallback ( ) ;
2019
+ // Backport v14.x: Session required for debugging stream object
2020
+ // debugStreamObj(this, 'shutting down writable on last write');
2021
+ shutdownWritable . call ( this , endCheckCallback ) ;
2022
+ } ) ;
2023
+
1965
2024
if ( writev )
1966
- req = writevGeneric ( this , data , cb ) ;
2025
+ req = writevGeneric ( this , data , writeCallback ) ;
1967
2026
else
1968
- req = writeGeneric ( this , data , encoding , cb ) ;
2027
+ req = writeGeneric ( this , data , encoding , writeCallback ) ;
1969
2028
1970
2029
trackWriteState ( this , req . bytes ) ;
1971
2030
}
@@ -1979,21 +2038,13 @@ class Http2Stream extends Duplex {
1979
2038
}
1980
2039
1981
2040
_final ( cb ) {
1982
- const handle = this [ kHandle ] ;
1983
2041
if ( this . pending ) {
1984
2042
this . once ( 'ready' , ( ) => this . _final ( cb ) ) ;
1985
- } else if ( handle !== undefined ) {
1986
- debugStreamObj ( this , '_final shutting down' ) ;
1987
- const req = new ShutdownWrap ( ) ;
1988
- req . oncomplete = afterShutdown ;
1989
- req . callback = cb ;
1990
- req . handle = handle ;
1991
- const err = handle . shutdown ( req ) ;
1992
- if ( err === 1 ) // synchronous finish
1993
- return afterShutdown . call ( req , 0 ) ;
1994
- } else {
1995
- cb ( ) ;
2043
+ return ;
1996
2044
}
2045
+ // Backport v14.x: Session required for debugging stream object
2046
+ // debugStreamObj(this, 'shutting down writable on _final');
2047
+ shutdownWritable . call ( this , cb ) ;
1997
2048
}
1998
2049
1999
2050
_read ( nread ) {
@@ -2098,11 +2149,20 @@ class Http2Stream extends Duplex {
2098
2149
debugStream ( this [ kID ] || 'pending' , session [ kType ] , 'destroying stream' ) ;
2099
2150
2100
2151
const state = this [ kState ] ;
2101
- const sessionCode = session [ kState ] . goawayCode ||
2102
- session [ kState ] . destroyCode ;
2103
- const code = err != null ?
2104
- sessionCode || NGHTTP2_INTERNAL_ERROR :
2105
- state . rstCode || sessionCode ;
2152
+ const sessionState = session [ kState ] ;
2153
+ const sessionCode = sessionState . goawayCode || sessionState . destroyCode ;
2154
+
2155
+ // If a stream has already closed successfully, there is no error
2156
+ // to report from this stream, even if the session has errored.
2157
+ // This can happen if the stream was already in process of destroying
2158
+ // after a successful close, but the session had a error between
2159
+ // this stream's close and destroy operations.
2160
+ // Previously, this always overrode a successful close operation code
2161
+ // NGHTTP2_NO_ERROR (0) with sessionCode because the use of the || operator.
2162
+ const code = ( err != null ?
2163
+ ( sessionCode || NGHTTP2_INTERNAL_ERROR ) :
2164
+ ( this . closed ? this . rstCode : sessionCode )
2165
+ ) ;
2106
2166
const hasHandle = handle !== undefined ;
2107
2167
2108
2168
if ( ! this . closed )
@@ -2111,13 +2171,13 @@ class Http2Stream extends Duplex {
2111
2171
2112
2172
if ( hasHandle ) {
2113
2173
handle . destroy ( ) ;
2114
- session [ kState ] . streams . delete ( id ) ;
2174
+ sessionState . streams . delete ( id ) ;
2115
2175
} else {
2116
- session [ kState ] . pendingStreams . delete ( this ) ;
2176
+ sessionState . pendingStreams . delete ( this ) ;
2117
2177
}
2118
2178
2119
2179
// Adjust the write queue size for accounting
2120
- session [ kState ] . writeQueueSize -= state . writeQueueSize ;
2180
+ sessionState . writeQueueSize -= state . writeQueueSize ;
2121
2181
state . writeQueueSize = 0 ;
2122
2182
2123
2183
// RST code 8 not emitted as an error as its used by clients to signify
0 commit comments