@@ -869,4 +869,78 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
869869 XCTAssertEqual ( releaseAction. request, . none)
870870 XCTAssertNoThrow ( try connections. closeConnection ( http2Conn) )
871871 }
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+ }
872946}
0 commit comments