@@ -869,4 +869,78 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
869
869
XCTAssertEqual ( releaseAction. request, . none)
870
870
XCTAssertNoThrow ( try connections. closeConnection ( http2Conn) )
871
871
}
872
+
873
+ func testConnectionIsImmediatelyCreatedAfterBackoffTimerFires( ) {
874
+ let elg = EmbeddedEventLoopGroup ( loops: 2 )
875
+ let el1 = elg. next ( )
876
+ let el2 = elg. next ( )
877
+ var connections = MockConnectionPool ( )
878
+ var queuer = MockRequestQueuer ( )
879
+ var state = HTTPConnectionPool . StateMachine ( idGenerator: . init( ) , maximumConcurrentHTTP1Connections: 8 )
880
+
881
+ var connectionIDs : [ HTTPConnectionPool . Connection . ID ] = [ ]
882
+ for el in [ el1, el2, el2] {
883
+ let mockRequest = MockHTTPRequest ( eventLoop: el, requiresEventLoopForChannel: true )
884
+ let request = HTTPConnectionPool . Request ( mockRequest)
885
+ let action = state. executeRequest ( request)
886
+ guard case . createConnection( let connID, let eventLoop) = action. connection else {
887
+ return XCTFail ( " Unexpected connection action \( action. connection) " )
888
+ }
889
+ connectionIDs. append ( connID)
890
+ XCTAssertTrue ( eventLoop === el)
891
+ XCTAssertEqual ( action. request, . scheduleRequestTimeout( for: request, on: mockRequest. eventLoop) )
892
+ XCTAssertNoThrow ( try connections. createConnection ( connID, on: el) )
893
+ XCTAssertNoThrow ( try queuer. queue ( mockRequest, id: request. id) )
894
+ }
895
+
896
+ // fail the two connections for el2
897
+ for connectionID in connectionIDs. dropFirst ( ) {
898
+ struct SomeError : Error { }
899
+ XCTAssertNoThrow ( try connections. failConnectionCreation ( connectionID) )
900
+ let action = state. failedToCreateNewConnection ( SomeError ( ) , connectionID: connectionID)
901
+ XCTAssertEqual ( action. request, . none)
902
+ guard case . scheduleBackoffTimer( connectionID, backoff: _, on: _) = action. connection else {
903
+ return XCTFail ( " unexpected connection action \( connectionID) " )
904
+ }
905
+ XCTAssertNoThrow ( try connections. startConnectionBackoffTimer ( connectionID) )
906
+ }
907
+ let http2ConnID1 = connectionIDs [ 0 ]
908
+ let http2ConnID2 = connectionIDs [ 1 ]
909
+ let http2ConnID3 = connectionIDs [ 2 ]
910
+
911
+ // let the first connection on el1 succeed as a http2 connection
912
+ let http2Conn1 : HTTPConnectionPool . Connection = . __testOnly_connection( id: http2ConnID1, eventLoop: el1)
913
+ XCTAssertNoThrow ( try connections. succeedConnectionCreationHTTP2 ( http2ConnID1, maxConcurrentStreams: 10 ) )
914
+ let migrationAction1 = state. newHTTP2ConnectionCreated ( http2Conn1, maxConcurrentStreams: 10 )
915
+ guard case . executeRequestsAndCancelTimeouts( let requests, http2Conn1) = migrationAction1. request else {
916
+ return XCTFail ( " unexpected request action \( migrationAction1. request) " )
917
+ }
918
+ XCTAssertEqual ( migrationAction1. connection, . migration( createConnections: [ ] , closeConnections: [ ] , scheduleTimeout: nil ) )
919
+ XCTAssertEqual ( requests. count, 1 )
920
+ for request in requests {
921
+ XCTAssertNoThrow ( try queuer. get ( request. id, request: request. __testOnly_wrapped_request ( ) ) )
922
+ XCTAssertNoThrow ( try connections. execute ( request. __testOnly_wrapped_request ( ) , on: http2Conn1) )
923
+ }
924
+
925
+ // we now have 1 active connection on el1 and 2 backing off connections on el2
926
+ // with 2 queued requests with a requirement to be executed on el2
927
+
928
+ // if the backoff timer fires for a connection on el2, we should immediately start a new connection
929
+ XCTAssertNoThrow ( try connections. connectionBackoffTimerDone ( http2ConnID2) )
930
+ let action2 = state. connectionCreationBackoffDone ( http2ConnID2)
931
+ XCTAssertEqual ( action2. request, . none)
932
+ guard case . createConnection( let newHttp2ConnID2, let eventLoop2) = action2. connection else {
933
+ return XCTFail ( " Unexpected connection action \( action2. connection) " )
934
+ }
935
+ XCTAssertTrue ( eventLoop2 === el2)
936
+ XCTAssertNoThrow ( try connections. createConnection ( newHttp2ConnID2, on: el2) )
937
+
938
+ // we now have a starting connection for el2 and another one backing off
939
+
940
+ // if the backoff timer fires now for a connection on el2, we should *not* start a new connection
941
+ XCTAssertNoThrow ( try connections. connectionBackoffTimerDone ( http2ConnID3) )
942
+ let action3 = state. connectionCreationBackoffDone ( http2ConnID3)
943
+ XCTAssertEqual ( action3. request, . none)
944
+ XCTAssertEqual ( action3. connection, . none)
945
+ }
872
946
}
0 commit comments