@@ -154,6 +154,7 @@ const STREAM_FLAGS_CLOSED = 0x2;
154154const STREAM_FLAGS_HEADERS_SENT = 0x4 ;
155155const STREAM_FLAGS_HEAD_REQUEST = 0x8 ;
156156const STREAM_FLAGS_ABORTED = 0x10 ;
157+ const STREAM_FLAGS_HAS_TRAILERS = 0x20 ;
157158
158159const SESSION_FLAGS_PENDING = 0x0 ;
159160const SESSION_FLAGS_READY = 0x1 ;
@@ -278,27 +279,14 @@ function onStreamClose(code) {
278279 if ( stream . destroyed )
279280 return ;
280281
281- const state = stream [ kState ] ;
282-
283282 debug ( `Http2Stream ${ stream [ kID ] } [Http2Session ` +
284283 `${ sessionName ( stream [ kSession ] [ kType ] ) } ]: closed with code ${ code } ` ) ;
285284
286- if ( ! stream . closed ) {
287- // Unenroll from timeouts
288- unenroll ( stream ) ;
289- stream . removeAllListeners ( 'timeout' ) ;
290-
291- // Set the state flags
292- state . flags |= STREAM_FLAGS_CLOSED ;
293- state . rstCode = code ;
294-
295- // Close the writable side of the stream
296- abort ( stream ) ;
297- stream . end ( ) ;
298- }
285+ if ( ! stream . closed )
286+ closeStream ( stream , code , false ) ;
299287
300- if ( state . fd !== undefined )
301- tryClose ( state . fd ) ;
288+ if ( stream [ kState ] . fd !== undefined )
289+ tryClose ( stream [ kState ] . fd ) ;
302290
303291 // Defer destroy we actually emit end.
304292 if ( stream . _readableState . endEmitted || code !== NGHTTP2_NO_ERROR ) {
@@ -454,7 +442,7 @@ function requestOnConnect(headers, options) {
454442
455443 // At this point, the stream should have already been destroyed during
456444 // the session.destroy() method. Do nothing else.
457- if ( session . destroyed )
445+ if ( session === undefined || session . destroyed )
458446 return ;
459447
460448 // If the session was closed while waiting for the connect, destroy
@@ -1369,6 +1357,9 @@ class ClientHttp2Session extends Http2Session {
13691357 if ( options . endStream )
13701358 stream . end ( ) ;
13711359
1360+ if ( options . waitForTrailers )
1361+ stream [ kState ] . flags |= STREAM_FLAGS_HAS_TRAILERS ;
1362+
13721363 const onConnect = requestOnConnect . bind ( stream , headersList , options ) ;
13731364 if ( this . connecting ) {
13741365 this . on ( 'connect' , onConnect ) ;
@@ -1425,32 +1416,70 @@ function afterDoStreamWrite(status, handle, req) {
14251416}
14261417
14271418function streamOnResume ( ) {
1428- if ( ! this . destroyed && ! this . pending )
1419+ if ( ! this . destroyed && ! this . pending ) {
1420+ if ( ! this [ kState ] . didRead )
1421+ this [ kState ] . didRead = true ;
14291422 this [ kHandle ] . readStart ( ) ;
1423+ }
14301424}
14311425
14321426function streamOnPause ( ) {
14331427 if ( ! this . destroyed && ! this . pending )
14341428 this [ kHandle ] . readStop ( ) ;
14351429}
14361430
1437- // If the writable side of the Http2Stream is still open, emit the
1438- // 'aborted' event and set the aborted flag.
1439- function abort ( stream ) {
1440- if ( ! stream . aborted &&
1441- ! ( stream . _writableState . ended || stream . _writableState . ending ) ) {
1442- stream [ kState ] . flags |= STREAM_FLAGS_ABORTED ;
1443- stream . emit ( 'aborted' ) ;
1444- }
1445- }
1446-
14471431function afterShutdown ( ) {
14481432 this . callback ( ) ;
14491433 const stream = this . handle [ kOwner ] ;
14501434 if ( stream )
14511435 stream [ kMaybeDestroy ] ( ) ;
14521436}
14531437
1438+ function closeStream ( stream , code , shouldSubmitRstStream = true ) {
1439+ const state = stream [ kState ] ;
1440+ state . flags |= STREAM_FLAGS_CLOSED ;
1441+ state . rstCode = code ;
1442+
1443+ // Clear timeout and remove timeout listeners
1444+ stream . setTimeout ( 0 ) ;
1445+ stream . removeAllListeners ( 'timeout' ) ;
1446+
1447+ const { ending, finished } = stream . _writableState ;
1448+
1449+ if ( ! ending ) {
1450+ // If the writable side of the Http2Stream is still open, emit the
1451+ // 'aborted' event and set the aborted flag.
1452+ if ( ! stream . aborted ) {
1453+ state . flags |= STREAM_FLAGS_ABORTED ;
1454+ stream . emit ( 'aborted' ) ;
1455+ }
1456+
1457+ // Close the writable side.
1458+ stream . end ( ) ;
1459+ }
1460+
1461+ if ( shouldSubmitRstStream ) {
1462+ const finishFn = finishCloseStream . bind ( stream , code ) ;
1463+ if ( ! ending || finished || code !== NGHTTP2_NO_ERROR )
1464+ finishFn ( ) ;
1465+ else
1466+ stream . once ( 'finish' , finishFn ) ;
1467+ }
1468+ }
1469+
1470+ function finishCloseStream ( code ) {
1471+ const rstStreamFn = submitRstStream . bind ( this , code ) ;
1472+ // If the handle has not yet been assigned, queue up the request to
1473+ // ensure that the RST_STREAM frame is sent after the stream ID has
1474+ // been determined.
1475+ if ( this . pending ) {
1476+ this . push ( null ) ;
1477+ this . once ( 'ready' , rstStreamFn ) ;
1478+ return ;
1479+ }
1480+ rstStreamFn ( ) ;
1481+ }
1482+
14541483// An Http2Stream is a Duplex stream that is backed by a
14551484// node::http2::Http2Stream handle implementing StreamBase.
14561485class Http2Stream extends Duplex {
@@ -1468,6 +1497,7 @@ class Http2Stream extends Duplex {
14681497 session [ kState ] . pendingStreams . add ( this ) ;
14691498
14701499 this [ kState ] = {
1500+ didRead : false ,
14711501 flags : STREAM_FLAGS_PENDING ,
14721502 rstCode : NGHTTP2_NO_ERROR ,
14731503 writeQueueSize : 0 ,
@@ -1749,6 +1779,8 @@ class Http2Stream extends Duplex {
17491779 throw headersList ;
17501780 this [ kSentTrailers ] = headers ;
17511781
1782+ this [ kState ] . flags &= ~ STREAM_FLAGS_HAS_TRAILERS ;
1783+
17521784 const ret = this [ kHandle ] . trailers ( headersList ) ;
17531785 if ( ret < 0 )
17541786 this . destroy ( new NghttpError ( ret ) ) ;
@@ -1779,38 +1811,13 @@ class Http2Stream extends Duplex {
17791811 if ( callback !== undefined && typeof callback !== 'function' )
17801812 throw new errors . TypeError ( 'ERR_INVALID_CALLBACK' ) ;
17811813
1782- // Unenroll the timeout.
1783- unenroll ( this ) ;
1784- this . removeAllListeners ( 'timeout' ) ;
1785-
1786- // Close the writable
1787- abort ( this ) ;
1788- this . end ( ) ;
1789-
17901814 if ( this . closed )
17911815 return ;
17921816
1793- const state = this [ kState ] ;
1794- state . flags |= STREAM_FLAGS_CLOSED ;
1795- state . rstCode = code ;
1796-
1797- if ( callback !== undefined ) {
1817+ if ( callback !== undefined )
17981818 this . once ( 'close' , callback ) ;
1799- }
18001819
1801- if ( this [ kHandle ] === undefined )
1802- return ;
1803-
1804- const rstStreamFn = submitRstStream . bind ( this , code ) ;
1805- // If the handle has not yet been assigned, queue up the request to
1806- // ensure that the RST_STREAM frame is sent after the stream ID has
1807- // been determined.
1808- if ( this . pending ) {
1809- this . push ( null ) ;
1810- this . once ( 'ready' , rstStreamFn ) ;
1811- return ;
1812- }
1813- rstStreamFn ( ) ;
1820+ closeStream ( this , code ) ;
18141821 }
18151822
18161823 // Called by this.destroy().
@@ -1825,24 +1832,19 @@ class Http2Stream extends Duplex {
18251832 debug ( `Http2Stream ${ this [ kID ] || '<pending>' } [Http2Session ` +
18261833 `${ sessionName ( session [ kType ] ) } ]: destroying stream` ) ;
18271834 const state = this [ kState ] ;
1828- const code = state . rstCode =
1829- err != null ?
1830- NGHTTP2_INTERNAL_ERROR :
1831- state . rstCode || NGHTTP2_NO_ERROR ;
1832- if ( handle !== undefined ) {
1833- // If the handle exists, we need to close, then destroy the handle
1834- this . close ( code ) ;
1835- if ( ! this . _readableState . ended && ! this . _readableState . ending )
1836- this . push ( null ) ;
1835+ const code = err != null ?
1836+ NGHTTP2_INTERNAL_ERROR : ( state . rstCode || NGHTTP2_NO_ERROR ) ;
1837+
1838+ const hasHandle = handle !== undefined ;
1839+
1840+ if ( ! this . closed )
1841+ closeStream ( this , code , hasHandle ) ;
1842+ this . push ( null ) ;
1843+
1844+ if ( hasHandle ) {
18371845 handle . destroy ( ) ;
18381846 session [ kState ] . streams . delete ( id ) ;
18391847 } else {
1840- unenroll ( this ) ;
1841- this . removeAllListeners ( 'timeout' ) ;
1842- state . flags |= STREAM_FLAGS_CLOSED ;
1843- abort ( this ) ;
1844- this . end ( ) ;
1845- this . push ( null ) ;
18461848 session [ kState ] . pendingStreams . delete ( this ) ;
18471849 }
18481850
@@ -1878,13 +1880,23 @@ class Http2Stream extends Duplex {
18781880 }
18791881
18801882 // TODO(mcollina): remove usage of _*State properties
1881- if ( this . _readableState . ended &&
1882- this . _writableState . ended &&
1883- this . _writableState . pendingcb === 0 &&
1884- this . closed ) {
1885- this . destroy ( ) ;
1886- // This should return, but eslint complains.
1887- // return
1883+ if ( this . _writableState . ended && this . _writableState . pendingcb === 0 ) {
1884+ if ( this . _readableState . ended && this . closed ) {
1885+ this . destroy ( ) ;
1886+ return ;
1887+ }
1888+
1889+ // We've submitted a response from our server session, have not attempted
1890+ // to process any incoming data, and have no trailers. This means we can
1891+ // attempt to gracefully close the session.
1892+ const state = this [ kState ] ;
1893+ if ( this . headersSent &&
1894+ this [ kSession ] [ kType ] === NGHTTP2_SESSION_SERVER &&
1895+ ! ( state . flags & STREAM_FLAGS_HAS_TRAILERS ) &&
1896+ ! state . didRead &&
1897+ ! this . _readableState . resumeScheduled ) {
1898+ this . close ( ) ;
1899+ }
18881900 }
18891901 }
18901902}
@@ -2045,7 +2057,6 @@ function afterOpen(session, options, headers, streamOptions, err, fd) {
20452057 }
20462058 if ( this . destroyed || this . closed ) {
20472059 tryClose ( fd ) ;
2048- abort ( this ) ;
20492060 return ;
20502061 }
20512062 state . fd = fd ;
@@ -2181,8 +2192,10 @@ class ServerHttp2Stream extends Http2Stream {
21812192 if ( options . endStream )
21822193 streamOptions |= STREAM_OPTION_EMPTY_PAYLOAD ;
21832194
2184- if ( options . waitForTrailers )
2195+ if ( options . waitForTrailers ) {
21852196 streamOptions |= STREAM_OPTION_GET_TRAILERS ;
2197+ state . flags |= STREAM_FLAGS_HAS_TRAILERS ;
2198+ }
21862199
21872200 headers = processHeaders ( headers ) ;
21882201 const statusCode = headers [ HTTP2_HEADER_STATUS ] |= 0 ;
@@ -2248,8 +2261,10 @@ class ServerHttp2Stream extends Http2Stream {
22482261 }
22492262
22502263 let streamOptions = 0 ;
2251- if ( options . waitForTrailers )
2264+ if ( options . waitForTrailers ) {
22522265 streamOptions |= STREAM_OPTION_GET_TRAILERS ;
2266+ this [ kState ] . flags |= STREAM_FLAGS_HAS_TRAILERS ;
2267+ }
22532268
22542269 if ( typeof fd !== 'number' )
22552270 throw new errors . TypeError ( 'ERR_INVALID_ARG_TYPE' ,
@@ -2315,8 +2330,10 @@ class ServerHttp2Stream extends Http2Stream {
23152330 }
23162331
23172332 let streamOptions = 0 ;
2318- if ( options . waitForTrailers )
2333+ if ( options . waitForTrailers ) {
23192334 streamOptions |= STREAM_OPTION_GET_TRAILERS ;
2335+ this [ kState ] . flags |= STREAM_FLAGS_HAS_TRAILERS ;
2336+ }
23202337
23212338 const session = this [ kSession ] ;
23222339 debug ( `Http2Stream ${ this [ kID ] } [Http2Session ` +
0 commit comments