@@ -174,7 +174,7 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
174174 [ ConditionalTheory ]
175175 [ MsQuicSupported ]
176176 [ InlineData ( HttpProtocols . Http3 , 11 ) ]
177- [ InlineData ( HttpProtocols . Http3 , 1024 , Skip = "HttpClient issue https://github.com/dotnet/runtime/issues/56115" ) ]
177+ [ InlineData ( HttpProtocols . Http3 , 1024 ) ]
178178 [ InlineData ( HttpProtocols . Http2 , 11 ) ]
179179 [ InlineData ( HttpProtocols . Http2 , 1024 ) ]
180180 public async Task GET_ServerStreaming_ClientReadsPartialResponse ( HttpProtocols protocol , int clientBufferSize )
@@ -219,7 +219,7 @@ public async Task GET_ServerStreaming_ClientReadsPartialResponse(HttpProtocols p
219219
220220 [ ConditionalTheory ]
221221 [ MsQuicSupported ]
222- [ InlineData ( HttpProtocols . Http3 , Skip = "https://github.com/dotnet/runtime/issues/56969" ) ]
222+ [ InlineData ( HttpProtocols . Http3 ) ]
223223 [ InlineData ( HttpProtocols . Http2 ) ]
224224 public async Task POST_ClientSendsOnlyHeaders_RequestReceivedOnServer ( HttpProtocols protocol )
225225 {
@@ -514,6 +514,164 @@ public async Task GET_MultipleRequestsInSequence_ReusedState()
514514 }
515515 }
516516
517+ // Verify HTTP/2 and HTTP/3 match behavior
518+ [ ConditionalTheory ]
519+ [ MsQuicSupported ]
520+ [ InlineData ( HttpProtocols . Http3 , Skip = "https://github.com/dotnet/runtime/issues/56129" ) ]
521+ [ InlineData ( HttpProtocols . Http2 ) ]
522+ public async Task POST_ClientCancellationBidirectional_RequestAbortRaised ( HttpProtocols protocol )
523+ {
524+ // Arrange
525+ var cancelledTcs = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
526+ var readAsyncTask = new TaskCompletionSource < Task > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
527+ var clientHasCancelledSyncPoint = new SyncPoint ( ) ;
528+
529+ var builder = CreateHostBuilder ( async context =>
530+ {
531+ context . RequestAborted . Register ( ( ) =>
532+ {
533+ Logger . LogInformation ( "Server received request aborted." ) ;
534+ cancelledTcs . SetResult ( ) ;
535+ } ) ;
536+
537+ var requestBody = context . Request . Body ;
538+ var responseBody = context . Response . Body ;
539+
540+ // Read content
541+ var data = await requestBody . ReadAtLeastLengthAsync ( TestData . Length ) ;
542+
543+ await responseBody . WriteAsync ( data ) ;
544+ await responseBody . FlushAsync ( ) ;
545+
546+ await clientHasCancelledSyncPoint . WaitForSyncPoint ( ) . DefaultTimeout ( ) ;
547+ clientHasCancelledSyncPoint . Continue ( ) ;
548+
549+ for ( var i = 0 ; i < 5 ; i ++ )
550+ {
551+ await Task . Delay ( 100 ) ;
552+
553+ Logger . LogInformation ( $ "Server writing to response after cancellation { i } .") ;
554+ await responseBody . WriteAsync ( data ) ;
555+ await responseBody . FlushAsync ( ) ;
556+ }
557+
558+ // Wait for task cancellation
559+ await cancelledTcs . Task ;
560+
561+ readAsyncTask . SetResult ( requestBody . ReadAsync ( data ) . AsTask ( ) ) ;
562+ } , protocol : protocol ) ;
563+
564+ var httpClientHandler = new HttpClientHandler ( ) ;
565+ httpClientHandler . ServerCertificateCustomValidationCallback = HttpClientHandler . DangerousAcceptAnyServerCertificateValidator ;
566+
567+ using ( var host = builder . Build ( ) )
568+ using ( var client = new HttpClient ( httpClientHandler ) )
569+ {
570+ await host . StartAsync ( ) . DefaultTimeout ( ) ;
571+
572+ var cts = new CancellationTokenSource ( ) ;
573+ var requestContent = new StreamingHttpContext ( ) ;
574+
575+ var request = new HttpRequestMessage ( HttpMethod . Post , $ "https://127.0.0.1:{ host . GetPort ( ) } /") ;
576+ request . Content = requestContent ;
577+ request . Version = GetProtocol ( protocol ) ;
578+ request . VersionPolicy = HttpVersionPolicy . RequestVersionExact ;
579+
580+ // Act
581+ var responseTask = client . SendAsync ( request , HttpCompletionOption . ResponseHeadersRead ) ;
582+
583+ var requestStream = await requestContent . GetStreamAsync ( ) . DefaultTimeout ( ) ;
584+
585+ // Send headers
586+ await requestStream . FlushAsync ( ) . DefaultTimeout ( ) ;
587+ // Write content
588+ await requestStream . WriteAsync ( TestData ) . DefaultTimeout ( ) ;
589+
590+ var response = await responseTask . DefaultTimeout ( ) ;
591+
592+ var responseStream = await response . Content . ReadAsStreamAsync ( ) . DefaultTimeout ( ) ;
593+
594+ var data = await responseStream . ReadAtLeastLengthAsync ( TestData . Length ) . DefaultTimeout ( ) ;
595+
596+ Logger . LogInformation ( "Client canceled request." ) ;
597+ response . Dispose ( ) ;
598+
599+ await clientHasCancelledSyncPoint . WaitToContinue ( ) . DefaultTimeout ( ) ;
600+
601+ // Assert
602+ await cancelledTcs . Task . DefaultTimeout ( ) ;
603+
604+ var serverWriteTask = await readAsyncTask . Task . DefaultTimeout ( ) ;
605+
606+ await Assert . ThrowsAnyAsync < Exception > ( ( ) => serverWriteTask ) . DefaultTimeout ( ) ;
607+
608+ await host . StopAsync ( ) . DefaultTimeout ( ) ;
609+ }
610+
611+ // Ensure this log wasn't written:
612+ // Critical: Http3OutputProducer.ProcessDataWrites observed an unexpected exception.
613+ var badLogWrite = TestSink . Writes . FirstOrDefault ( w => w . LogLevel == LogLevel . Critical ) ;
614+ if ( badLogWrite != null )
615+ {
616+ Assert . True ( false , "Bad log write: " + badLogWrite + Environment . NewLine + badLogWrite . Exception ) ;
617+ }
618+ }
619+
620+ // Verify HTTP/2 and HTTP/3 match behavior
621+ [ ConditionalTheory ]
622+ [ MsQuicSupported ]
623+ [ InlineData ( HttpProtocols . Http3 , Skip = "https://github.com/dotnet/runtime/issues/56129" ) ]
624+ [ InlineData ( HttpProtocols . Http2 ) ]
625+ public async Task GET_ClientCancellationAfterResponseHeaders_RequestAbortRaised ( HttpProtocols protocol )
626+ {
627+ // Arrange
628+ var cancelledTcs = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
629+
630+ var builder = CreateHostBuilder ( async context =>
631+ {
632+ context . RequestAborted . Register ( ( ) =>
633+ {
634+ Logger . LogInformation ( "Server received request aborted." ) ;
635+ cancelledTcs . SetResult ( ) ;
636+ } ) ;
637+
638+ var responseBody = context . Response . Body ;
639+ await responseBody . WriteAsync ( TestData ) ;
640+ await responseBody . FlushAsync ( ) ;
641+
642+ // Wait for task cancellation
643+ await cancelledTcs . Task ;
644+ } , protocol : protocol ) ;
645+
646+ var httpClientHandler = new HttpClientHandler ( ) ;
647+ httpClientHandler . ServerCertificateCustomValidationCallback = HttpClientHandler . DangerousAcceptAnyServerCertificateValidator ;
648+
649+ using ( var host = builder . Build ( ) )
650+ using ( var client = new HttpClient ( httpClientHandler ) )
651+ {
652+ await host . StartAsync ( ) . DefaultTimeout ( ) ;
653+
654+ var request = new HttpRequestMessage ( HttpMethod . Get , $ "https://127.0.0.1:{ host . GetPort ( ) } /") ;
655+ request . Version = GetProtocol ( protocol ) ;
656+ request . VersionPolicy = HttpVersionPolicy . RequestVersionExact ;
657+
658+ // Act
659+ var response = await client . SendAsync ( request , HttpCompletionOption . ResponseHeadersRead ) ;
660+
661+ var responseStream = await response . Content . ReadAsStreamAsync ( ) . DefaultTimeout ( ) ;
662+
663+ var data = await responseStream . ReadAtLeastLengthAsync ( TestData . Length ) . DefaultTimeout ( ) ;
664+
665+ Logger . LogInformation ( "Client canceled request." ) ;
666+ response . Dispose ( ) ;
667+
668+ // Assert
669+ await cancelledTcs . Task . DefaultTimeout ( ) ;
670+
671+ await host . StopAsync ( ) . DefaultTimeout ( ) ;
672+ }
673+ }
674+
517675 [ ConditionalFact ]
518676 [ MsQuicSupported ]
519677 public async Task GET_MultipleRequests_ConnectionAndTraceIdsUpdated ( )
0 commit comments