44using System . Diagnostics ;
55using System . IO ;
66using System . Runtime . CompilerServices ;
7+ using System . Runtime . ExceptionServices ;
78using System . Runtime . InteropServices ;
89using System . Threading ;
910using System . Threading . Tasks ;
@@ -359,38 +360,40 @@ public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationTo
359360 /// <param name="buffer">The region of memory to write data from.</param>
360361 /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None"/>.</param>
361362 /// <param name="completeWrites">Notifies the peer about gracefully closing the write side, i.e.: sends FIN flag with the data.</param>
362- public async ValueTask WriteAsync ( ReadOnlyMemory < byte > buffer , bool completeWrites , CancellationToken cancellationToken = default )
363+ public ValueTask WriteAsync ( ReadOnlyMemory < byte > buffer , bool completeWrites , CancellationToken cancellationToken = default )
363364 {
364- ObjectDisposedException . ThrowIf ( _disposed == 1 , this ) ;
365+ if ( _disposed == 1 )
366+ {
367+ return ValueTask . FromException ( ExceptionDispatchInfo . SetCurrentStackTrace ( new ObjectDisposedException ( nameof ( QuicStream ) ) ) ) ;
368+ }
365369
366370 if ( ! _canWrite )
367371 {
368- throw new InvalidOperationException ( SR . net_quic_writing_notallowed ) ;
372+ return ValueTask . FromException ( ExceptionDispatchInfo . SetCurrentStackTrace ( new InvalidOperationException ( SR . net_quic_writing_notallowed ) ) ) ;
369373 }
370374
371375 if ( NetEventSource . Log . IsEnabled ( ) )
372376 {
373377 NetEventSource . Info ( this , $ "{ this } Stream writing memory of '{ buffer . Length } ' bytes while { ( completeWrites ? "completing" : "not completing" ) } writes.") ;
374378 }
375379
376- if ( _sendTcs . IsCompleted )
380+ if ( _sendTcs . IsCompleted && cancellationToken . IsCancellationRequested )
377381 {
378382 // Special case exception type for pre-canceled token while we've already transitioned to a final state and don't need to abort write.
379383 // It must happen before we try to get the value task, since the task source is versioned and each instance must be awaited.
380- cancellationToken . ThrowIfCancellationRequested ( ) ;
384+ return ValueTask . FromCanceled ( cancellationToken ) ;
381385 }
382386
383387 // Concurrent call, this one lost the race.
384388 if ( ! _sendTcs . TryGetValueTask ( out ValueTask valueTask , this , cancellationToken ) )
385389 {
386- throw new InvalidOperationException ( SR . Format ( SR . net_io_invalidnestedcall , "write" ) ) ;
390+ return ValueTask . FromException ( ExceptionDispatchInfo . SetCurrentStackTrace ( new InvalidOperationException ( SR . Format ( SR . net_io_invalidnestedcall , "write" ) ) ) ) ;
387391 }
388392
389393 // No need to call anything since we already have a result, most likely an exception.
390394 if ( valueTask . IsCompleted )
391395 {
392- await valueTask . ConfigureAwait ( false ) ;
393- return ;
396+ return valueTask ;
394397 }
395398
396399 // For an empty buffer complete immediately, close the writing side of the stream if necessary.
@@ -401,8 +404,7 @@ public async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, bool completeWrit
401404 {
402405 CompleteWrites ( ) ;
403406 }
404- await valueTask . ConfigureAwait ( false ) ;
405- return ;
407+ return valueTask ;
406408 }
407409
408410 // We own the lock, abort might happen, but exception will get stored instead.
@@ -438,7 +440,7 @@ public async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, bool completeWrit
438440 }
439441 }
440442
441- await valueTask . ConfigureAwait ( false ) ;
443+ return valueTask ;
442444 }
443445
444446 /// <summary>
0 commit comments