@@ -42,6 +42,7 @@ const {
42
42
validateQuicEndpointOptions,
43
43
validateCreateSecureContextOptions,
44
44
validateQuicSocketConnectOptions,
45
+ QuicStreamSharedState,
45
46
QuicSocketSharedState,
46
47
QuicSessionSharedState,
47
48
QLogStream,
@@ -1792,8 +1793,7 @@ class QuicSession extends EventEmitter {
1792
1793
const stream = this [ kInternalState ] . streams . get ( id ) ;
1793
1794
if ( stream === undefined )
1794
1795
return ;
1795
-
1796
- stream . destroy ( ) ;
1796
+ stream [ kDestroy ] ( code ) ;
1797
1797
}
1798
1798
1799
1799
[ kStreamReset ] ( id , code ) {
@@ -1968,6 +1968,8 @@ class QuicSession extends EventEmitter {
1968
1968
return ;
1969
1969
state . destroyed = true ;
1970
1970
1971
+ state . idleTimeout = Boolean ( this [ kInternalState ] . state ?. idleTimeout ) ;
1972
+
1971
1973
// Destroy any remaining streams immediately.
1972
1974
for ( const stream of state . streams . values ( ) )
1973
1975
stream . destroy ( error ) ;
@@ -1982,7 +1984,6 @@ class QuicSession extends EventEmitter {
1982
1984
handle . stats [ IDX_QUIC_SESSION_STATS_DESTROYED_AT ] =
1983
1985
process . hrtime . bigint ( ) ;
1984
1986
state . stats = new BigInt64Array ( handle . stats ) ;
1985
- state . idleTimeout = this [ kInternalState ] . state . idleTimeout ;
1986
1987
1987
1988
// Destroy the underlying QuicSession handle
1988
1989
handle . destroy ( state . closeCode , state . closeFamily ) ;
@@ -2530,10 +2531,12 @@ function streamOnPause() {
2530
2531
if ( ! this . destroyed )
2531
2532
this [ kHandle ] . readStop ( ) ;
2532
2533
}
2533
-
2534
2534
class QuicStream extends Duplex {
2535
2535
[ kInternalState ] = {
2536
2536
closed : false ,
2537
+ closePromise : undefined ,
2538
+ closePromiseReject : undefined ,
2539
+ closePromiseResolve : undefined ,
2537
2540
defaultEncoding : undefined ,
2538
2541
didRead : false ,
2539
2542
id : undefined ,
@@ -2544,6 +2547,7 @@ class QuicStream extends Duplex {
2544
2547
dataRateHistogram : undefined ,
2545
2548
dataSizeHistogram : undefined ,
2546
2549
dataAckHistogram : undefined ,
2550
+ sharedState : undefined ,
2547
2551
stats : undefined ,
2548
2552
} ;
2549
2553
@@ -2563,7 +2567,7 @@ class QuicStream extends Duplex {
2563
2567
allowHalfOpen : true ,
2564
2568
decodeStrings : true ,
2565
2569
emitClose : true ,
2566
- autoDestroy : false ,
2570
+ autoDestroy : true ,
2567
2571
captureRejections : true ,
2568
2572
} ) ;
2569
2573
const state = this [ kInternalState ] ;
@@ -2584,7 +2588,6 @@ class QuicStream extends Duplex {
2584
2588
// is still minimally usable before this but any data
2585
2589
// written will be buffered until kSetHandle is called.
2586
2590
[ kSetHandle ] ( handle ) {
2587
- this [ kHandle ] = handle ;
2588
2591
const state = this [ kInternalState ] ;
2589
2592
if ( handle !== undefined ) {
2590
2593
handle . onread = onStreamRead ;
@@ -2594,23 +2597,84 @@ class QuicStream extends Duplex {
2594
2597
state . dataRateHistogram = new Histogram ( handle . rate ) ;
2595
2598
state . dataSizeHistogram = new Histogram ( handle . size ) ;
2596
2599
state . dataAckHistogram = new Histogram ( handle . ack ) ;
2600
+ state . sharedState = new QuicStreamSharedState ( handle . state ) ;
2597
2601
state . session [ kAddStream ] ( state . id , this ) ;
2598
2602
} else {
2603
+ if ( this [ kHandle ] !== undefined ) {
2604
+ this [ kHandle ] . stats [ IDX_QUIC_STREAM_STATS_DESTROYED_AT ] =
2605
+ process . hrtime . bigint ( ) ;
2606
+ state . stats = new BigInt64Array ( this [ kHandle ] . stats ) ;
2607
+ }
2608
+ state . sharedState = undefined ;
2599
2609
if ( state . dataRateHistogram )
2600
2610
state . dataRateHistogram [ kDestroyHistogram ] ( ) ;
2601
2611
if ( state . dataSizeHistogram )
2602
2612
state . dataSizeHistogram [ kDestroyHistogram ] ( ) ;
2603
2613
if ( state . dataAckHistogram )
2604
2614
state . dataAckHistogram [ kDestroyHistogram ] ( ) ;
2605
2615
}
2616
+ this [ kHandle ] = handle ;
2606
2617
}
2607
2618
2608
2619
[ kStreamReset ] ( code ) {
2609
- this [ kInternalState ] . resetCode = code | 0 ;
2620
+ // Receiving a reset from the peer indicates that it is no
2621
+ // longer sending any data, we can safely close the readable
2622
+ // side of the Duplex here.
2623
+ this [ kInternalState ] . resetCode = code ;
2610
2624
this . push ( null ) ;
2611
2625
this . read ( ) ;
2612
2626
}
2613
2627
2628
+ [ kClose ] ( ) {
2629
+ const state = this [ kInternalState ] ;
2630
+
2631
+ if ( this . destroyed ) {
2632
+ return PromiseReject (
2633
+ new ERR_INVALID_STATE ( 'QuicStream is already destroyed' ) ) ;
2634
+ }
2635
+
2636
+ const promise = deferredClosePromise ( state ) ;
2637
+ if ( this . readable ) {
2638
+ this . push ( null ) ;
2639
+ this . read ( ) ;
2640
+ }
2641
+
2642
+ if ( this . writable ) {
2643
+ this . end ( ) ;
2644
+ }
2645
+
2646
+ return promise ;
2647
+ }
2648
+
2649
+ close ( ) {
2650
+ return this [ kInternalState ] . closePromise || this [ kClose ] ( ) ;
2651
+ }
2652
+
2653
+ _destroy ( error , callback ) {
2654
+ const state = this [ kInternalState ] ;
2655
+ const handle = this [ kHandle ] ;
2656
+ this [ kSetHandle ] ( ) ;
2657
+ if ( handle !== undefined )
2658
+ handle . destroy ( ) ;
2659
+ state . session [ kRemoveStream ] ( this ) ;
2660
+
2661
+ if ( error && typeof state . closePromiseReject === 'function' )
2662
+ state . closePromiseReject ( error ) ;
2663
+ else if ( typeof state . closePromiseResolve === 'function' )
2664
+ state . closePromiseResolve ( ) ;
2665
+
2666
+ process . nextTick ( ( ) => callback ( error ) ) ;
2667
+ }
2668
+
2669
+ [ kDestroy ] ( code ) {
2670
+ // TODO(@jasnell): If code is non-zero, and stream is not otherwise
2671
+ // naturally shutdown, then we should destroy with an error.
2672
+
2673
+ // Put the QuicStream into detached mode before calling destroy
2674
+ this [ kSetHandle ] ( ) ;
2675
+ this . destroy ( ) ;
2676
+ }
2677
+
2614
2678
[ kHeaders ] ( headers , kind , push_id ) {
2615
2679
// TODO(@jasnell): Convert the headers into a proper object
2616
2680
let name ;
@@ -2635,42 +2699,6 @@ class QuicStream extends Duplex {
2635
2699
process . nextTick ( emit . bind ( this , name , headers , push_id ) ) ;
2636
2700
}
2637
2701
2638
- [ kClose ] ( family , code ) {
2639
- const state = this [ kInternalState ] ;
2640
- // Trigger the abrupt shutdown of the stream. If the stream is
2641
- // already no-longer readable or writable, this does nothing. If
2642
- // the stream is readable or writable, then the abort event will
2643
- // be emitted immediately after triggering the send of the
2644
- // RESET_STREAM and STOP_SENDING frames. The stream will no longer
2645
- // be readable or writable, but will not be immediately destroyed
2646
- // as we need to wait until ngtcp2 recognizes the stream as
2647
- // having been closed to be destroyed.
2648
-
2649
- // Do nothing if we've already been destroyed
2650
- if ( this . destroyed || state . closed )
2651
- return ;
2652
-
2653
- state . closed = true ;
2654
-
2655
- // Trigger scheduling of the RESET_STREAM and STOP_SENDING frames
2656
- // as appropriate. Notify ngtcp2 that the stream is to be shutdown.
2657
- // Once sent, the stream will be closed and destroyed as soon as
2658
- // the shutdown is acknowledged by the peer.
2659
- this [ kHandle ] . resetStream ( code , family ) ;
2660
-
2661
- // Close down the readable side of the stream
2662
- if ( this . readable ) {
2663
- this . push ( null ) ;
2664
- this . read ( ) ;
2665
- }
2666
-
2667
- // It is important to call shutdown on the handle before shutting
2668
- // down the writable side of the stream in order to prevent an
2669
- // empty STREAM frame with fin set to be sent to the peer.
2670
- if ( this . writable )
2671
- this . end ( ) ;
2672
- }
2673
-
2674
2702
[ kAfterAsyncWrite ] ( { bytes } ) {
2675
2703
// TODO(@jasnell): Implement this
2676
2704
}
@@ -2681,6 +2709,7 @@ class QuicStream extends Duplex {
2681
2709
const initiated = this . serverInitiated ? 'server' : 'client' ;
2682
2710
return customInspect ( this , {
2683
2711
id : this [ kInternalState ] . id ,
2712
+ detached : this . detached ,
2684
2713
direction,
2685
2714
initiated,
2686
2715
writableState : this . _writableState ,
@@ -2699,6 +2728,15 @@ class QuicStream extends Duplex {
2699
2728
// TODO(@jasnell): Implement this later
2700
2729
}
2701
2730
2731
+ get detached ( ) {
2732
+ // The QuicStream is detached if it is yet destroyed
2733
+ // but the underlying handle is undefined. While in
2734
+ // detached mode, the QuicStream may still have
2735
+ // data pending in the read queue, but writes will
2736
+ // not be permitted.
2737
+ return this [ kHandle ] === undefined ;
2738
+ }
2739
+
2702
2740
get serverInitiated ( ) {
2703
2741
return ! ! ( this [ kInternalState ] . id & 0b01 ) ;
2704
2742
}
@@ -2740,20 +2778,40 @@ class QuicStream extends Duplex {
2740
2778
// called. By calling shutdown, we're telling
2741
2779
// the native side that no more data will be
2742
2780
// coming so that a fin stream packet can be
2743
- // sent.
2781
+ // sent, allowing any remaining final stream
2782
+ // frames to be sent if necessary.
2783
+ //
2784
+ // When end() is called, we set the writeEnded
2785
+ // flag so that we can know earlier when there
2786
+ // is not going to be any more data being written
2787
+ // but that is only used when end() is called
2788
+ // with a final chunk to write.
2744
2789
_final ( cb ) {
2745
- const handle = this [ kHandle ] ;
2746
- if ( handle === undefined ) {
2747
- cb ( ) ;
2790
+ if ( ! this . detached ) {
2791
+ const state = this [ kInternalState ] ;
2792
+ if ( state . sharedState ?. finSent )
2793
+ return cb ( ) ;
2794
+ const handle = this [ kHandle ] ;
2795
+ const req = new ShutdownWrap ( ) ;
2796
+ req . oncomplete = ( ) => {
2797
+ req . handle = undefined ;
2798
+ cb ( ) ;
2799
+ } ;
2800
+ req . handle = handle ;
2801
+ if ( handle . shutdown ( req ) === 1 )
2802
+ return req . oncomplete ( ) ;
2748
2803
return ;
2749
2804
}
2805
+ return cb ( ) ;
2806
+ }
2750
2807
2751
- const req = new ShutdownWrap ( ) ;
2752
- req . oncomplete = ( ) => cb ( ) ;
2753
- req . handle = handle ;
2754
- const err = handle . shutdown ( req ) ;
2755
- if ( err === 1 )
2756
- return cb ( ) ;
2808
+ end ( ...args ) {
2809
+ if ( ! this . destroyed ) {
2810
+ if ( ! this . detached )
2811
+ this [ kInternalState ] . sharedState . writeEnded = true ;
2812
+ super . end . apply ( this , args ) ;
2813
+ }
2814
+ return this ;
2757
2815
}
2758
2816
2759
2817
_read ( nread ) {
@@ -2809,11 +2867,6 @@ class QuicStream extends Duplex {
2809
2867
this [ kUpdateTimer ] ( ) ;
2810
2868
this . ownsFd = ownsFd ;
2811
2869
2812
- // Close the writable side of the stream, but only as far as the writable
2813
- // stream implementation is concerned.
2814
- this . _final = null ;
2815
- this . end ( ) ;
2816
-
2817
2870
defaultTriggerAsyncIdScope ( this [ async_id_symbol ] ,
2818
2871
QuicStream [ kStartFilePipe ] ,
2819
2872
this , fd , offset , length ) ;
@@ -2840,6 +2893,7 @@ class QuicStream extends Duplex {
2840
2893
this . source . close ( ) . catch ( stream . destroy . bind ( stream ) ) ;
2841
2894
else
2842
2895
this . source . releaseFD ( ) ;
2896
+ stream . end ( ) ;
2843
2897
}
2844
2898
2845
2899
static [ kOnPipedFileHandleRead ] ( ) {
@@ -2869,35 +2923,14 @@ class QuicStream extends Duplex {
2869
2923
return this [ kInternalState ] . push_id ;
2870
2924
}
2871
2925
2872
- close ( code ) {
2873
- this [ kClose ] ( QUIC_ERROR_APPLICATION , code ) ;
2926
+ _onTimeout ( ) {
2927
+ // TODO(@jasnell): Implement this
2874
2928
}
2875
2929
2876
2930
get session ( ) {
2877
2931
return this [ kInternalState ] . session ;
2878
2932
}
2879
2933
2880
- _destroy ( error , callback ) {
2881
- const state = this [ kInternalState ] ;
2882
- const handle = this [ kHandle ] ;
2883
- // Do not use handle after this point as the underlying C++
2884
- // object has been destroyed. Any attempt to use the object
2885
- // will segfault and crash the process.
2886
- if ( handle !== undefined ) {
2887
- handle . stats [ IDX_QUIC_STREAM_STATS_DESTROYED_AT ] =
2888
- process . hrtime . bigint ( ) ;
2889
- state . stats = new BigInt64Array ( handle . stats ) ;
2890
- handle . destroy ( ) ;
2891
- }
2892
- state . session [ kRemoveStream ] ( this ) ;
2893
- // The destroy callback must be invoked in a nextTick
2894
- process . nextTick ( ( ) => callback ( error ) ) ;
2895
- }
2896
-
2897
- _onTimeout ( ) {
2898
- // TODO(@jasnell): Implement this
2899
- }
2900
-
2901
2934
get dataRateHistogram ( ) {
2902
2935
return this [ kInternalState ] . dataRateHistogram ;
2903
2936
}
0 commit comments