@@ -879,6 +879,160 @@ public async Task GET_MultipleRequestsInSequence_ReusedState()
879
879
}
880
880
}
881
881
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
+
882
1036
[ ConditionalFact ]
883
1037
[ MsQuicSupported ]
884
1038
public async Task GET_MultipleRequests_RequestVersionOrHigher_UpgradeToHttp3 ( )
0 commit comments