@@ -701,6 +701,84 @@ await Http3Api.InitializeConnectionAsync(async c =>
701
701
await tcs . Task ;
702
702
}
703
703
704
+ [ Fact ]
705
+ public async Task ErrorCodeIsValidOnConnectionTimeout ( )
706
+ {
707
+ // This test loosely repros the scenario in https://github.com/dotnet/aspnetcore/issues/57933.
708
+ // In particular, there's a request from the server and, once a response has been sent,
709
+ // the (simulated) transport throws a QuicException that surfaces through AcceptAsync.
710
+ // This test confirms that Http3Connection.ProcessRequestsAsync doesn't (indirectly) cause
711
+ // IProtocolErrorCodeFeature.Error to be set to (or left at) -1, which System.Net.Quic will
712
+ // not accept.
713
+
714
+ // Used to signal that a request has been sent and a response has been received
715
+ var requestTcs = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
716
+ // Used to signal that the connection context has been aborted
717
+ var abortTcs = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
718
+
719
+ // InitializeConnectionAsync consumes the connection context, so set it first
720
+ Http3Api . MultiplexedConnectionContext = new ThrowingMultiplexedConnectionContext ( Http3Api , skipCount : 2 , requestTcs , abortTcs ) ;
721
+ await Http3Api . InitializeConnectionAsync ( _echoApplication ) ;
722
+
723
+ await Http3Api . CreateControlStream ( ) ;
724
+ await Http3Api . GetInboundControlStream ( ) ;
725
+ var requestStream = await Http3Api . CreateRequestStream ( Headers , endStream : true ) ;
726
+ var responseHeaders = await requestStream . ExpectHeadersAsync ( ) ;
727
+
728
+ await requestStream . ExpectReceiveEndOfStream ( ) ;
729
+ await requestStream . OnDisposedTask . DefaultTimeout ( ) ;
730
+
731
+ requestTcs . SetResult ( ) ;
732
+
733
+ // By the time the connection context is aborted, the error code feature has been updated
734
+ await abortTcs . Task . DefaultTimeout ( ) ;
735
+
736
+ Http3Api . CloseServerGracefully ( ) ;
737
+
738
+ var errorCodeFeature = Http3Api . MultiplexedConnectionContext . Features . Get < IProtocolErrorCodeFeature > ( ) ;
739
+ Assert . InRange ( errorCodeFeature . Error , 0 , ( 1L << 62 ) - 1 ) ; // Valid range for HTTP/3 error codes
740
+ }
741
+
742
+ private sealed class ThrowingMultiplexedConnectionContext : TestMultiplexedConnectionContext
743
+ {
744
+ private int _skipCount ;
745
+ private readonly TaskCompletionSource _requestTcs ;
746
+ private readonly TaskCompletionSource _abortTcs ;
747
+
748
+ /// <summary>
749
+ /// After <paramref name="skipCount"/> calls to <see cref="AcceptAsync"/>, the next call will throw a <see cref="QuicException"/>
750
+ /// (after waiting for <see cref="_requestTcs"/> to be set).
751
+ ///
752
+ /// <paramref name="abortTcs"/> lets this type signal that <see cref="Abort"/> has been called.
753
+ /// </summary>
754
+ public ThrowingMultiplexedConnectionContext ( Http3InMemory testBase , int skipCount , TaskCompletionSource requestTcs , TaskCompletionSource abortTcs )
755
+ : base ( testBase )
756
+ {
757
+ _skipCount = skipCount ;
758
+ _requestTcs = requestTcs ;
759
+ _abortTcs = abortTcs ;
760
+ }
761
+
762
+ public override async ValueTask < ConnectionContext > AcceptAsync ( CancellationToken cancellationToken = default )
763
+ {
764
+ if ( _skipCount -- <= 0 )
765
+ {
766
+ await _requestTcs . Task . DefaultTimeout ( ) ;
767
+ throw new System . Net . Quic . QuicException (
768
+ System . Net . Quic . QuicError . ConnectionTimeout ,
769
+ applicationErrorCode : null ,
770
+ "Connection timed out waiting for a response from the peer." ) ;
771
+ }
772
+ return await base . AcceptAsync ( cancellationToken ) ;
773
+ }
774
+
775
+ public override void Abort ( ConnectionAbortedException abortReason )
776
+ {
777
+ _abortTcs . SetResult ( ) ;
778
+ base . Abort ( abortReason ) ;
779
+ }
780
+ }
781
+
704
782
private async Task < ConnectionContext > MakeRequestAsync ( int index , KeyValuePair < string , string > [ ] headers , bool sendData , bool waitForServerDispose )
705
783
{
706
784
var requestStream = await Http3Api . CreateRequestStream ( headers , endStream : ! sendData ) ;
0 commit comments