1- // Flags: --experimental-quic --experimental-stream-iter --no-warnings
2-
3- // Test: Quic maxstreamdata updates on http/3
4- // Client sends a body that precisely fills the window size,
5- // and verifies that it is data transfer is not stalled.
6-
7- import { hasQuic , skip , mustCall } from '../common/index.mjs' ;
8- import assert from 'node:assert' ;
9- import { readFile } from 'node:fs/promises' ;
10- import { setTimeout as sleep } from 'node:timers/promises' ;
11-
12- if ( ! hasQuic ) {
13- skip ( 'QUIC is not enabled' ) ;
14- }
15- const { listen, connect } = await import ( 'node:quic' ) ;
16- const { createPrivateKey } = await import ( 'node:crypto' ) ;
17- const { drainableProtocol } = await import ( 'stream/iter' ) ;
18-
19- const keys = 'test/fixtures/keys' ;
20- const key = createPrivateKey ( await readFile ( `${ keys } /agent1-key.pem` ) ) ;
21- const cert = await readFile ( `${ keys } /agent1-cert.pem` ) ;
22-
23- const WINDOW = 4096 ;
24- // Fills the window exactly: HTTP/3 spends 11 of those bytes on framing (8 for
25- // the HEADERS frame below, 3 for the DATA frame header). The send buffer then
26- // empties at the same moment the window reaches zero, leaving nothing in
27- // flight to ack. Any other size leaves bytes queued, and the ack for those
28- // wakes the writer instead, hiding the bug.
29- const BODY = WINDOW - 11 ;
30-
31- let letServerRead ;
32- const serverMayRead = new Promise ( ( resolve ) => { letServerRead = resolve ; } ) ;
33-
34- const endpoint = await listen ( ( session ) => {
35- session . onstream = async ( stream ) => {
36- await serverMayRead ;
37- for await ( const _ of stream ) { /* reading extends the window */ }
38- } ;
39- } , {
40- sni : { '*' : { keys : [ key ] , certs : [ cert ] } } ,
41- transportParams : {
42- initialMaxStreamDataBidiRemote : WINDOW ,
43- initialMaxData : 1024 * 1024 ,
44- } ,
45- onheaders ( ) { this . sendHeaders ( { ':status' : '200' } ) ; } ,
46- } ) ;
47-
48- const session = await connect ( endpoint . address , {
49- servername : 'localhost' ,
50- verifyPeer : 'manual' ,
51- } ) ;
52- await session . opened ;
53-
54- // Budget well above the window, so the window is what stops the writer.
55- const stream = await session . createBidirectionalStream ( { budget : 1024 * 1024 } ) ;
56- stream . sendHeaders ( {
57- ':method' : 'POST' ,
58- ':path' : '/' ,
59- ':scheme' : 'https' ,
60- ':authority' : 'localhost' ,
61- } , { terminal : false } ) ;
62-
63- const writer = stream . writer ;
64- writer . writeSync ( new Uint8Array ( BODY ) ) ;
65-
66- // Long enough for every byte to be acked. The peer acks as data arrives,
67- // whether or not its application has read any of it, so by now the window is
68- // exhausted, the send buffer is empty, and no further ACK can arrive.
69- await sleep ( 500 ) ;
70-
71- const watchdog = setTimeout ( ( ) => {
72- console . error ( 'STALLED: no drain after MAX_STREAM_DATA' ) ;
73- process . exit ( 1 ) ;
74- } , 5000 ) ;
75-
76- letServerRead ( ) ; // extend the window, with no ack attached
77- await writer [ drainableProtocol ] ( ) ;
78-
79- clearTimeout ( watchdog ) ;
80- process . exit ( 0 ) ;
1+ // Flags: --experimental-quic --experimental-stream-iter --no-warnings
2+
3+ // Test: Quic maxstreamdata updates on http/3
4+ // Client sends a body that precisely fills the window size,
5+ // and verifies that it is data transfer is not stalled.
6+
7+ import { hasQuic , skip } from '../common/index.mjs' ;
8+ import { readFile } from 'node:fs/promises' ;
9+ import { setTimeout as sleep } from 'node:timers/promises' ;
10+
11+ if ( ! hasQuic ) {
12+ skip ( 'QUIC is not enabled' ) ;
13+ }
14+ const { listen, connect } = await import ( 'node:quic' ) ;
15+ const { createPrivateKey } = await import ( 'node:crypto' ) ;
16+ const { drainableProtocol } = await import ( 'stream/iter' ) ;
17+
18+ const keys = 'test/fixtures/keys' ;
19+ const key = createPrivateKey ( await readFile ( `${ keys } /agent1-key.pem` ) ) ;
20+ const cert = await readFile ( `${ keys } /agent1-cert.pem` ) ;
21+
22+ const WINDOW = 4096 ;
23+ // Fills the window exactly: HTTP/3 spends 11 of those bytes on framing (8 for
24+ // the HEADERS frame below, 3 for the DATA frame header). The send buffer then
25+ // empties at the same moment the window reaches zero, leaving nothing in
26+ // flight to ack. Any other size leaves bytes queued, and the ack for those
27+ // wakes the writer instead, hiding the bug.
28+ const BODY = WINDOW - 11 ;
29+
30+ let letServerRead ;
31+ const serverMayRead = new Promise ( ( resolve ) => { letServerRead = resolve ; } ) ;
32+
33+ const endpoint = await listen ( ( session ) => {
34+ session . onstream = async ( stream ) => {
35+ await serverMayRead ;
36+ for await ( const _ of stream ) {
37+ void _ ; /* reading extends the window */
38+ }
39+ } ;
40+ } , {
41+ sni : { '*' : { keys : [ key ] , certs : [ cert ] } } ,
42+ transportParams : {
43+ initialMaxStreamDataBidiRemote : WINDOW ,
44+ initialMaxData : 1024 * 1024 ,
45+ } ,
46+ onheaders ( ) { this . sendHeaders ( { ':status' : '200' } ) ; } ,
47+ } ) ;
48+
49+ const session = await connect ( endpoint . address , {
50+ servername : 'localhost' ,
51+ verifyPeer : 'manual' ,
52+ } ) ;
53+ await session . opened ;
54+
55+ // Budget well above the window, so the window is what stops the writer.
56+ const stream = await session . createBidirectionalStream ( { budget : 1024 * 1024 } ) ;
57+ stream . sendHeaders ( {
58+ ':method' : 'POST' ,
59+ ':path' : '/' ,
60+ ':scheme' : 'https' ,
61+ ':authority' : 'localhost' ,
62+ } , { terminal : false } ) ;
63+
64+ const writer = stream . writer ;
65+ writer . writeSync ( new Uint8Array ( BODY ) ) ;
66+
67+ // Long enough for every byte to be acked. The peer acks as data arrives,
68+ // whether or not its application has read any of it, so by now the window is
69+ // exhausted, the send buffer is empty, and no further ACK can arrive.
70+ await sleep ( 500 ) ;
71+
72+ const watchdog = setTimeout ( ( ) => {
73+ console . error ( 'STALLED: no drain after MAX_STREAM_DATA' ) ;
74+ process . exit ( 1 ) ;
75+ } , 5000 ) ;
76+
77+ letServerRead ( ) ; // Extend the window, with no ack attached
78+ await writer [ drainableProtocol ] ( ) ;
79+
80+ clearTimeout ( watchdog ) ;
81+ process . exit ( 0 ) ;
0 commit comments