@@ -687,7 +687,7 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
687
687
guard case . createConnection( let conn1ID, let eventLoop) = action. connection else {
688
688
return XCTFail ( " Unexpected connection action \( action. connection) " )
689
689
}
690
-
690
+
691
691
XCTAssertTrue ( eventLoop === el1)
692
692
XCTAssertEqual ( action. request, . scheduleRequestTimeout( for: request, on: mockRequest. eventLoop) )
693
693
XCTAssertNoThrow ( try connections. createConnection ( conn1ID, on: el1) )
@@ -698,7 +698,6 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
698
698
guard case . failRequestsAndCancelTimeouts( let requestsToCancel, let error) = shutdownAction. request else {
699
699
return XCTFail ( " unexpected shutdown action \( shutdownAction) " )
700
700
}
701
-
702
701
XCTAssertEqualTypeAndValue ( error, HTTPClientError . cancelled)
703
702
704
703
for request in requestsToCancel {
@@ -916,6 +915,78 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
916
915
XCTAssertNoThrow ( try connections. closeConnection ( http2Conn) )
917
916
}
918
917
918
+ func testHTTP2toHTTP1MigrationDuringShutdown( ) {
919
+ let elg = EmbeddedEventLoopGroup ( loops: 2 )
920
+ let el1 = elg. next ( )
921
+ let el2 = elg. next ( )
922
+ var connections = MockConnectionPool ( )
923
+ var queuer = MockRequestQueuer ( )
924
+ var state = HTTPConnectionPool . StateMachine ( idGenerator: . init( ) , maximumConcurrentHTTP1Connections: 8 )
925
+
926
+ // create http2 connection
927
+ let mockRequest = MockHTTPRequest ( eventLoop: el1)
928
+ let request1 = HTTPConnectionPool . Request ( mockRequest)
929
+ let action1 = state. executeRequest ( request1)
930
+ guard case . createConnection( let http2ConnID, let http2EventLoop) = action1. connection else {
931
+ return XCTFail ( " Unexpected connection action \( action1. connection) " )
932
+ }
933
+ XCTAssertTrue ( http2EventLoop === el1)
934
+ XCTAssertEqual ( action1. request, . scheduleRequestTimeout( for: request1, on: mockRequest. eventLoop) )
935
+ XCTAssertNoThrow ( try connections. createConnection ( http2ConnID, on: el1) )
936
+ XCTAssertNoThrow ( try queuer. queue ( mockRequest, id: request1. id) )
937
+ let http2Conn : HTTPConnectionPool . Connection = . __testOnly_connection( id: http2ConnID, eventLoop: el1)
938
+ XCTAssertNoThrow ( try connections. succeedConnectionCreationHTTP2 ( http2ConnID, maxConcurrentStreams: 10 ) )
939
+ let migrationAction1 = state. newHTTP2ConnectionCreated ( http2Conn, maxConcurrentStreams: 10 )
940
+ guard case . executeRequestsAndCancelTimeouts( let requests, http2Conn) = migrationAction1. request else {
941
+ return XCTFail ( " unexpected request action \( migrationAction1. request) " )
942
+ }
943
+ XCTAssertEqual ( migrationAction1. connection, . migration( createConnections: [ ] , closeConnections: [ ] , scheduleTimeout: nil ) )
944
+ XCTAssertEqual ( requests. count, 1 )
945
+ for request in requests {
946
+ XCTAssertNoThrow ( try queuer. get ( request. id, request: request. __testOnly_wrapped_request ( ) ) )
947
+ XCTAssertNoThrow ( try connections. execute ( request. __testOnly_wrapped_request ( ) , on: http2Conn) )
948
+ }
949
+
950
+ // a request with new required event loop should create a new connection
951
+ let mockRequestWithRequiredEventLoop = MockHTTPRequest ( eventLoop: el2, requiresEventLoopForChannel: true )
952
+ let requestWithRequiredEventLoop = HTTPConnectionPool . Request ( mockRequestWithRequiredEventLoop)
953
+ let action2 = state. executeRequest ( requestWithRequiredEventLoop)
954
+ guard case . createConnection( let http1ConnId, let http1EventLoop) = action2. connection else {
955
+ return XCTFail ( " Unexpected connection action \( action2. connection) " )
956
+ }
957
+ XCTAssertTrue ( http1EventLoop === el2)
958
+ XCTAssertEqual ( action2. request, . scheduleRequestTimeout( for: requestWithRequiredEventLoop, on: mockRequestWithRequiredEventLoop. eventLoop) )
959
+ XCTAssertNoThrow ( try connections. createConnection ( http1ConnId, on: el2) )
960
+ XCTAssertNoThrow ( try queuer. queue ( mockRequestWithRequiredEventLoop, id: requestWithRequiredEventLoop. id) )
961
+
962
+ /// we now no longer want anything of it
963
+ let shutdownAction = state. shutdown ( )
964
+ guard case . failRequestsAndCancelTimeouts( let requestsToCancel, let error) = shutdownAction. request else {
965
+ return XCTFail ( " unexpected shutdown action \( shutdownAction) " )
966
+ }
967
+ XCTAssertEqualTypeAndValue ( error, HTTPClientError . cancelled)
968
+
969
+ for request in requestsToCancel {
970
+ XCTAssertNoThrow ( try queuer. cancel ( request. id) )
971
+ }
972
+ XCTAssertTrue ( queuer. isEmpty)
973
+
974
+ // if we established a new http/1 connection we should migrate back to http/1,
975
+ // close the connection and shutdown the pool
976
+ let http1Conn : HTTPConnectionPool . Connection = . __testOnly_connection( id: http1ConnId, eventLoop: el2)
977
+ XCTAssertNoThrow ( try connections. succeedConnectionCreationHTTP1 ( http1ConnId) )
978
+ let migrationAction2 = state. newHTTP1ConnectionCreated ( http1Conn)
979
+ XCTAssertEqual ( migrationAction2. request, . none)
980
+ XCTAssertEqual ( migrationAction2. connection, . migration( createConnections: [ ] , closeConnections: [ http1Conn] , scheduleTimeout: nil ) )
981
+
982
+ // in http/1 state, we should close idle http2 connections
983
+ XCTAssertNoThrow ( try connections. finishExecution ( http2Conn. id) )
984
+ let releaseAction = state. http2ConnectionStreamClosed ( http2Conn. id)
985
+ XCTAssertEqual ( releaseAction. connection, . closeConnection( http2Conn, isShutdown: . yes( unclean: true ) ) )
986
+ XCTAssertEqual ( releaseAction. request, . none)
987
+ XCTAssertNoThrow ( try connections. closeConnection ( http2Conn) )
988
+ }
989
+
919
990
func testConnectionIsImmediatelyCreatedAfterBackoffTimerFires( ) {
920
991
let elg = EmbeddedEventLoopGroup ( loops: 2 )
921
992
let el1 = elg. next ( )
0 commit comments