Skip to content

Commit 2933388

Browse files
committed
add testConnectionFailureBackoff and fix behaviour
1 parent a8679bf commit 2933388

File tree

2 files changed

+58
-3
lines changed

2 files changed

+58
-3
lines changed

Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP2StateMachine.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,11 +245,10 @@ extension HTTPConnectionPool {
245245
// The naming of `failConnection` is a little confusing here. All it does is moving the
246246
// connection state from `.backingOff` to `.closed` here. It also returns the
247247
// connection's index.
248-
guard let (index, _) = self.connections.failConnection(connectionID) else {
248+
guard let (index, context) = self.connections.failConnection(connectionID) else {
249249
preconditionFailure("Backing off a connection that is unknown to us?")
250250
}
251-
let (newConnectionID, eventLoop) = self.connections.createNewConnectionByReplacingClosedConnection(at: index)
252-
return .init(request: .none, connection: .createConnection(newConnectionID, on: eventLoop))
251+
return nextActionForFailedConnection(at: index, on: context.eventLoop)
253252
}
254253

255254
mutating func timeoutRequest(_ requestID: Request.ID) -> Action {

Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP2StateMachineTests.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,60 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
114114
isShutdown: .yes(unclean: false)
115115
))
116116
}
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+
}
117173
}

0 commit comments

Comments
 (0)