@@ -97,32 +97,48 @@ public async Task JsonRpc_Cancels_And_Disposes_Pending_Requests()
9797 [ Fact ]
9898 public async Task JsonRpc_Does_Not_Retain_Oversized_Receive_Buffer ( )
9999 {
100- using var receiveStream = new FrameThenEofStream ( CreateResponseFrame ( 2 * 1024 * 1024 ) ) ;
100+ var oversizedFrame = CreateResponseFrame (
101+ long . MaxValue ,
102+ "ignored" ,
103+ headerPaddingLength : 1024 * 1024 ) ;
104+ var carriedFrame = CreateResponseFrame ( 1 , "carried" ) ;
105+ using var receiveStream = new CoalescedFramesThenWaitStream ( oversizedFrame , carriedFrame ) ;
101106 using var rpc = new JsonRpcReflection ( Stream . Null , receiveStream ) ;
102107
108+ var carriedResponse = rpc . InvokeAsync < string > ( "pending" , args : null ) ;
103109 rpc . StartListening ( ) ;
104110
105- var completed = await Task . WhenAny (
111+ var responseCompleted = await Task . WhenAny (
112+ carriedResponse ,
113+ Task . Delay ( TimeSpan . FromSeconds ( 5 ) ) ) ;
114+ Assert . Same ( carriedResponse , responseCompleted ) ;
115+ Assert . Equal ( "carried" , await carriedResponse ) ;
116+ Assert . True ( receiveStream . FramesWereCoalesced ) ;
117+
118+ var readCompleted = await Task . WhenAny (
106119 receiveStream . PostFrameReadBufferSize ,
107120 Task . Delay ( TimeSpan . FromSeconds ( 5 ) ) ) ;
108- Assert . Same ( receiveStream . PostFrameReadBufferSize , completed ) ;
121+ Assert . Same ( receiveStream . PostFrameReadBufferSize , readCompleted ) ;
109122 Assert . InRange ( await receiveStream . PostFrameReadBufferSize , 1 , 1024 * 1024 ) ;
110123 }
111124
112- private static byte [ ] CreateResponseFrame ( int payloadLength )
125+ private static byte [ ] CreateResponseFrame ( long id , string result , int headerPaddingLength = 0 )
113126 {
114127 using var bodyStream = new MemoryStream ( ) ;
115128 using ( var writer = new Utf8JsonWriter ( bodyStream ) )
116129 {
117130 writer . WriteStartObject ( ) ;
118131 writer . WriteString ( "jsonrpc" , "2.0" ) ;
119- writer . WriteNumber ( "id" , long . MaxValue ) ;
120- writer . WriteString ( "result" , new string ( 'x' , payloadLength ) ) ;
132+ writer . WriteNumber ( "id" , id ) ;
133+ writer . WriteString ( "result" , result ) ;
121134 writer . WriteEndObject ( ) ;
122135 }
123136
124137 var body = bodyStream . ToArray ( ) ;
125- var header = Encoding . ASCII . GetBytes ( $ "Content-Length: { body . Length } \r \n \r \n ") ;
138+ var paddingHeader = headerPaddingLength > 0
139+ ? $ "X-Padding: { new string ( 'x' , headerPaddingLength ) } \r \n "
140+ : string . Empty ;
141+ var header = Encoding . ASCII . GetBytes ( $ "{ paddingHeader } Content-Length: { body . Length } \r \n \r \n ") ;
126142 var frame = new byte [ header . Length + body . Length ] ;
127143 header . CopyTo ( frame , 0 ) ;
128144 body . CopyTo ( frame , header . Length ) ;
@@ -239,12 +255,24 @@ public async Task<T> InvokeAsync<T>(string methodName, object?[]? args, Cancella
239255 public void Dispose ( ) => ( ( IDisposable ) _instance ) . Dispose ( ) ;
240256 }
241257
242- private sealed class FrameThenEofStream ( byte [ ] frame ) : Stream
258+ private sealed class CoalescedFramesThenWaitStream : Stream
243259 {
244260 private readonly TaskCompletionSource < int > _postFrameReadBufferSize =
245261 new ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
262+ private readonly byte [ ] _frames ;
263+ private readonly int _firstFrameLength ;
246264 private int _offset ;
247265
266+ public CoalescedFramesThenWaitStream ( byte [ ] firstFrame , byte [ ] secondFrame )
267+ {
268+ _firstFrameLength = firstFrame . Length ;
269+ _frames = new byte [ firstFrame . Length + secondFrame . Length ] ;
270+ firstFrame . CopyTo ( _frames , 0 ) ;
271+ secondFrame . CopyTo ( _frames , firstFrame . Length ) ;
272+ }
273+
274+ public bool FramesWereCoalesced { get ; private set ; }
275+
248276 public Task < int > PostFrameReadBufferSize => _postFrameReadBufferSize . Task ;
249277
250278 public override bool CanRead => true ;
@@ -257,19 +285,18 @@ private sealed class FrameThenEofStream(byte[] frame) : Stream
257285
258286 public override long Position { get => throw new NotSupportedException ( ) ; set => throw new NotSupportedException ( ) ; }
259287
260- public override int Read ( byte [ ] buffer , int offset , int count ) =>
261- ReadCore ( buffer . AsMemory ( offset , count ) ) ;
288+ public override int Read ( byte [ ] buffer , int offset , int count ) => throw new NotSupportedException ( ) ;
262289
263290 public override Task < int > ReadAsync ( byte [ ] buffer , int offset , int count , CancellationToken cancellationToken ) =>
264- Task . FromResult ( ReadCore ( buffer . AsMemory ( offset , count ) ) ) ;
291+ ReadCoreAsync ( buffer . AsMemory ( offset , count ) , cancellationToken ) . AsTask ( ) ;
265292
266293#if NET8_0_OR_GREATER
267294 public override
268295#else
269296 internal
270297#endif
271298 ValueTask < int > ReadAsync ( Memory < byte > destination , CancellationToken cancellationToken = default ) =>
272- new ( ReadCore ( destination ) ) ;
299+ ReadCoreAsync ( destination , cancellationToken ) ;
273300
274301 public override void Flush ( )
275302 {
@@ -281,18 +308,26 @@ public override void Flush()
281308
282309 public override void Write ( byte [ ] buffer , int offset , int count ) => throw new NotSupportedException ( ) ;
283310
284- private int ReadCore ( Memory < byte > destination )
311+ private ValueTask < int > ReadCoreAsync ( Memory < byte > destination , CancellationToken cancellationToken )
285312 {
286- if ( _offset >= frame . Length )
313+ if ( _offset >= _frames . Length )
287314 {
288315 _postFrameReadBufferSize . TrySetResult ( destination . Length ) ;
289- return 0 ;
316+ return new ValueTask < int > ( WaitForCancellationAsync ( cancellationToken ) ) ;
290317 }
291318
292- var bytesRead = Math . Min ( destination . Length , frame . Length - _offset ) ;
293- frame . AsMemory ( _offset , bytesRead ) . CopyTo ( destination ) ;
319+ var startingOffset = _offset ;
320+ var bytesRead = Math . Min ( destination . Length , _frames . Length - _offset ) ;
321+ _frames . AsMemory ( _offset , bytesRead ) . CopyTo ( destination ) ;
294322 _offset += bytesRead ;
295- return bytesRead ;
323+ FramesWereCoalesced |= startingOffset < _firstFrameLength && _offset == _frames . Length ;
324+ return new ValueTask < int > ( bytesRead ) ;
325+ }
326+
327+ private static async Task < int > WaitForCancellationAsync ( CancellationToken cancellationToken )
328+ {
329+ await Task . Delay ( Timeout . InfiniteTimeSpan , cancellationToken ) . ConfigureAwait ( false ) ;
330+ return 0 ;
296331 }
297332 }
298333
0 commit comments