@@ -114,4 +114,60 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
114
114
isShutdown: . yes( unclean: false )
115
115
) )
116
116
}
117
+
118
+ func testConnectionFailureBackoff( ) {
119
+ let elg = EmbeddedEventLoopGroup ( loops: 4 )
120
+ defer { XCTAssertNoThrow ( try elg. syncShutdownGracefully ( ) ) }
121
+
122
+ var state = HTTPConnectionPool . HTTP2StateMaschine (
123
+ idGenerator: . init( )
124
+ )
125
+
126
+ let mockRequest = MockHTTPRequest ( eventLoop: elg. next ( ) )
127
+ let request = HTTPConnectionPool . Request ( mockRequest)
128
+
129
+ let action = state. executeRequest ( request)
130
+ XCTAssertEqual ( . scheduleRequestTimeout( for: request, on: mockRequest. eventLoop) , action. request)
131
+
132
+ // 1. connection attempt
133
+ guard case . createConnection( let connectionID, on: let connectionEL) = action. connection else {
134
+ return XCTFail ( " Unexpected connection action: \( action. connection) " )
135
+ }
136
+ XCTAssert ( connectionEL === mockRequest. eventLoop) // XCTAssertIdentical not available on Linux
137
+
138
+ let failedConnect1 = state. failedToCreateNewConnection ( HTTPClientError . connectTimeout, connectionID: connectionID)
139
+ XCTAssertEqual ( failedConnect1. request, . none)
140
+ guard case . scheduleBackoffTimer( connectionID, let backoffTimeAmount1, _) = failedConnect1. connection else {
141
+ return XCTFail ( " Unexpected connection action: \( failedConnect1. connection) " )
142
+ }
143
+
144
+ // 2. connection attempt
145
+ let backoffDoneAction = state. connectionCreationBackoffDone ( connectionID)
146
+ XCTAssertEqual ( backoffDoneAction. request, . none)
147
+ guard case . createConnection( let newConnectionID, on: let newEventLoop) = backoffDoneAction. connection else {
148
+ return XCTFail ( " Unexpected connection action: \( backoffDoneAction. connection) " )
149
+ }
150
+ XCTAssertGreaterThan ( newConnectionID, connectionID)
151
+ XCTAssert ( connectionEL === newEventLoop) // XCTAssertIdentical not available on Linux
152
+
153
+ let failedConnect2 = state. failedToCreateNewConnection ( HTTPClientError . connectTimeout, connectionID: newConnectionID)
154
+ XCTAssertEqual ( failedConnect2. request, . none)
155
+ guard case . scheduleBackoffTimer( newConnectionID, let backoffTimeAmount2, _) = failedConnect2. connection else {
156
+ return XCTFail ( " Unexpected connection action: \( failedConnect2. connection) " )
157
+ }
158
+
159
+ XCTAssertNotEqual ( backoffTimeAmount2, backoffTimeAmount1)
160
+
161
+ // 3. request times out
162
+ let failRequest = state. timeoutRequest ( request. id)
163
+ guard case . failRequest( let requestToFail, let requestError, cancelTimeout: false ) = failRequest. request else {
164
+ return XCTFail ( " Unexpected request action: \( action. request) " )
165
+ }
166
+ XCTAssert ( requestToFail. __testOnly_wrapped_request ( ) === mockRequest) // XCTAssertIdentical not available on Linux
167
+ XCTAssertEqual ( requestError as? HTTPClientError , . connectTimeout)
168
+ XCTAssertEqual ( failRequest. connection, . none)
169
+
170
+ // 4. retry connection, but no more queued requests.
171
+ XCTAssertEqual ( state. connectionCreationBackoffDone ( newConnectionID) , . none)
172
+ }
117
173
}
0 commit comments