@@ -879,6 +879,160 @@ public async Task GET_MultipleRequestsInSequence_ReusedState()
879879 }
880880 }
881881
882+ [ ConditionalFact ]
883+ [ MsQuicSupported ]
884+ public async Task GET_RequestAbortedByClient_StateNotReused ( )
885+ {
886+ // Arrange
887+ object persistedState = null ;
888+ var requestCount = 0 ;
889+ var abortedTcs = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
890+ var requestStartedTcs = new TaskCompletionSource ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
891+
892+ var builder = CreateHostBuilder ( async context =>
893+ {
894+ requestCount ++ ;
895+ var persistentStateCollection = context . Features . Get < IPersistentStateFeature > ( ) . State ;
896+ if ( persistentStateCollection . TryGetValue ( "Counter" , out var value ) )
897+ {
898+ persistedState = value ;
899+ }
900+ persistentStateCollection [ "Counter" ] = requestCount ;
901+
902+ if ( requestCount == 1 )
903+ {
904+ // For the first request, wait for RequestAborted to fire before returning
905+ context . RequestAborted . Register ( ( ) =>
906+ {
907+ Logger . LogInformation ( "Server received cancellation" ) ;
908+ abortedTcs . SetResult ( ) ;
909+ } ) ;
910+
911+ // Signal that the request has started and is ready to be cancelled
912+ requestStartedTcs . SetResult ( ) ;
913+
914+ // Wait for the request to be aborted
915+ await abortedTcs . Task ;
916+ }
917+ } ) ;
918+
919+ using ( var host = builder . Build ( ) )
920+ using ( var client = HttpHelpers . CreateClient ( ) )
921+ {
922+ await host . StartAsync ( ) ;
923+
924+ // Act - Send first request and cancel it
925+ var cts1 = new CancellationTokenSource ( ) ;
926+ var request1 = new HttpRequestMessage ( HttpMethod . Get , $ "https://127.0.0.1:{ host . GetPort ( ) } /") ;
927+ request1 . Version = HttpVersion . Version30 ;
928+ request1 . VersionPolicy = HttpVersionPolicy . RequestVersionExact ;
929+
930+ var responseTask1 = client . SendAsync ( request1 , cts1 . Token ) ;
931+
932+ // Wait for the server to start processing the request
933+ await requestStartedTcs . Task . DefaultTimeout ( ) ;
934+
935+ // Cancel the first request
936+ cts1 . Cancel ( ) ;
937+ await Assert . ThrowsAnyAsync < OperationCanceledException > ( ( ) => responseTask1 ) . DefaultTimeout ( ) ;
938+
939+ // Wait for the server to process the abort
940+ await abortedTcs . Task . DefaultTimeout ( ) ;
941+
942+ // Store the state from the first (aborted) request
943+ var firstRequestState = persistedState ;
944+
945+ // Delay to ensure the stream has enough time to return to pool
946+ await Task . Delay ( 100 ) ;
947+
948+ // Send second request (should not reuse state from aborted request)
949+ var request2 = new HttpRequestMessage ( HttpMethod . Get , $ "https://127.0.0.1:{ host . GetPort ( ) } /") ;
950+ request2 . Version = HttpVersion . Version30 ;
951+ request2 . VersionPolicy = HttpVersionPolicy . RequestVersionExact ;
952+
953+ var response2 = await client . SendAsync ( request2 , CancellationToken . None ) ;
954+ response2 . EnsureSuccessStatusCode ( ) ;
955+ var secondRequestState = persistedState ;
956+
957+ // Assert
958+ // First request has no persisted state (it was aborted)
959+ Assert . Null ( firstRequestState ) ;
960+
961+ // Second request should also have no persisted state since the first request was aborted
962+ // and state should not be reused from aborted requests
963+ Assert . Null ( secondRequestState ) ;
964+
965+ await host . StopAsync ( ) ;
966+ }
967+ }
968+
969+ [ ConditionalFact ]
970+ [ MsQuicSupported ]
971+ public async Task GET_RequestAbortedByServer_StateNotReused ( )
972+ {
973+ // Arrange
974+ object persistedState = null ;
975+ var requestCount = 0 ;
976+
977+ var builder = CreateHostBuilder ( context =>
978+ {
979+ requestCount ++ ;
980+ var persistentStateCollection = context . Features . Get < IPersistentStateFeature > ( ) . State ;
981+ if ( persistentStateCollection . TryGetValue ( "Counter" , out var value ) )
982+ {
983+ persistedState = value ;
984+ }
985+ persistentStateCollection [ "Counter" ] = requestCount ;
986+
987+ if ( requestCount == 1 )
988+ {
989+ context . Abort ( ) ;
990+ }
991+
992+ return Task . CompletedTask ;
993+ } ) ;
994+
995+ using ( var host = builder . Build ( ) )
996+ using ( var client = HttpHelpers . CreateClient ( ) )
997+ {
998+ await host . StartAsync ( ) ;
999+
1000+ var request1 = new HttpRequestMessage ( HttpMethod . Get , $ "https://127.0.0.1:{ host . GetPort ( ) } /") ;
1001+ request1 . Version = HttpVersion . Version30 ;
1002+ request1 . VersionPolicy = HttpVersionPolicy . RequestVersionExact ;
1003+
1004+ var responseTask1 = client . SendAsync ( request1 , CancellationToken . None ) ;
1005+ var ex = await Assert . ThrowsAnyAsync < HttpRequestException > ( ( ) => responseTask1 ) . DefaultTimeout ( ) ;
1006+ var innerEx = Assert . IsType < HttpProtocolException > ( ex . InnerException ) ;
1007+ Assert . Equal ( Http3ErrorCode . InternalError , ( Http3ErrorCode ) innerEx . ErrorCode ) ;
1008+
1009+ // Store the state from the first (aborted) request
1010+ var firstRequestState = persistedState ;
1011+
1012+ // Delay to ensure the stream has enough time to return to pool
1013+ await Task . Delay ( 100 ) ;
1014+
1015+ // Send second request (should not reuse state from aborted request)
1016+ var request2 = new HttpRequestMessage ( HttpMethod . Get , $ "https://127.0.0.1:{ host . GetPort ( ) } /") ;
1017+ request2 . Version = HttpVersion . Version30 ;
1018+ request2 . VersionPolicy = HttpVersionPolicy . RequestVersionExact ;
1019+
1020+ var response2 = await client . SendAsync ( request2 , CancellationToken . None ) ;
1021+ response2 . EnsureSuccessStatusCode ( ) ;
1022+ var secondRequestState = persistedState ;
1023+
1024+ // Assert
1025+ // First request has no persisted state (it was aborted)
1026+ Assert . Null ( firstRequestState ) ;
1027+
1028+ // Second request should also have no persisted state since the first request was aborted
1029+ // and state should not be reused from aborted requests
1030+ Assert . Null ( secondRequestState ) ;
1031+
1032+ await host . StopAsync ( ) ;
1033+ }
1034+ }
1035+
8821036 [ ConditionalFact ]
8831037 [ MsQuicSupported ]
8841038 public async Task GET_MultipleRequests_RequestVersionOrHigher_UpgradeToHttp3 ( )
0 commit comments