@@ -40,10 +40,21 @@ describe('streams', () => {
4040 // track incoming stream
4141 metrics . trackMultiaddrConnection ( outbound )
4242
43+ // attach reader before data arrives so the stream dispatches message events
44+ const iterator = outbound [ Symbol . asyncIterator ] ( )
45+
4346 // send data to the remote over the tracked stream
4447 const data = Uint8Array . from ( [ 0 , 1 , 2 , 3 , 4 ] )
4548 inbound . send ( data )
4649
50+ const first = await Promise . race ( [
51+ iterator . next ( ) ,
52+ new Promise < never > ( ( _resolve , reject ) => setTimeout ( ( ) => reject ( new Error ( 'timed out waiting for first frame' ) ) , 200 ) )
53+ ] )
54+
55+ expect ( first . done ) . to . equal ( false )
56+ expect ( first . value ?. byteLength ) . to . equal ( data . length )
57+
4758 await Promise . all ( [
4859 pEvent ( inbound , 'close' ) ,
4960 outbound . close ( ) ,
@@ -78,6 +89,43 @@ describe('streams', () => {
7889 expect ( scrapedMetrics ) . to . include ( `libp2p_data_transfer_bytes_total{protocol="${ outbound . protocol } sent"} ${ data . length } ` )
7990 } )
8091
92+ it ( 'should not consume early protocol data before app listeners are attached' , async ( ) => {
93+ const [ outbound , inbound ] = await streamPair ( )
94+
95+ const metrics = prometheusMetrics ( ) ( {
96+ logger : defaultLogger ( )
97+ } )
98+
99+ // Track stream before any app-level reader/listener is attached
100+ metrics . trackProtocolStream ( outbound )
101+
102+ // Remote sends data first
103+ const data = Uint8Array . from ( [ 9 , 8 , 7 , 6 , 5 ] )
104+ inbound . send ( data )
105+
106+ // Allow data to arrive and be buffered
107+ await new Promise ( ( resolve ) => setTimeout ( resolve , 25 ) )
108+
109+ // App starts reading later - should still receive first frame
110+ const iterator = outbound [ Symbol . asyncIterator ] ( )
111+ const first = await Promise . race ( [
112+ iterator . next ( ) ,
113+ new Promise < never > ( ( _resolve , reject ) => setTimeout ( ( ) => reject ( new Error ( 'timed out waiting for first frame' ) ) , 200 ) )
114+ ] )
115+
116+ expect ( first . done ) . to . equal ( false )
117+ expect ( first . value ?. byteLength ) . to . equal ( data . length )
118+
119+ await Promise . all ( [
120+ pEvent ( inbound , 'close' ) ,
121+ outbound . close ( ) ,
122+ inbound . close ( )
123+ ] )
124+
125+ const scrapedMetrics = await client . register . metrics ( )
126+ expect ( scrapedMetrics ) . to . include ( `libp2p_data_transfer_bytes_total{protocol="${ inbound . protocol } received"} ${ data . length } ` )
127+ } )
128+
81129 it ( 'should track bytes received over outbound streams' , async ( ) => {
82130 const [ outbound , inbound ] = await streamPair ( )
83131
@@ -88,10 +136,21 @@ describe('streams', () => {
88136 // track incoming stream
89137 metrics . trackProtocolStream ( outbound )
90138
139+ // attach reader before data arrives so the stream dispatches message events
140+ const iterator = outbound [ Symbol . asyncIterator ] ( )
141+
91142 // send data from remote to local
92143 const data = Uint8Array . from ( [ 0 , 1 , 2 , 3 , 4 ] )
93144 inbound . send ( data )
94145
146+ const first = await Promise . race ( [
147+ iterator . next ( ) ,
148+ new Promise < never > ( ( _resolve , reject ) => setTimeout ( ( ) => reject ( new Error ( 'timed out waiting for first frame' ) ) , 200 ) )
149+ ] )
150+
151+ expect ( first . done ) . to . equal ( false )
152+ expect ( first . value ?. byteLength ) . to . equal ( data . length )
153+
95154 await Promise . all ( [
96155 pEvent ( inbound , 'close' ) ,
97156 outbound . close ( ) ,
0 commit comments