Skip to content

Commit 5bee16a

Browse files
authored
Switch over state in HTTPConnectionPool.HTTP2StateMachine.failedToCreateNewConnection (swift-server#647)
1 parent fd03ed0 commit 5bee16a

File tree

3 files changed

+61
-13
lines changed

3 files changed

+61
-13
lines changed

Diff for: Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+HTTP2StateMachine.swift

+22-13
Original file line numberDiff line numberDiff line change
@@ -404,25 +404,34 @@ extension HTTPConnectionPool {
404404
}
405405

406406
mutating func failedToCreateNewConnection(_ error: Error, connectionID: Connection.ID) -> Action {
407-
// TODO: switch over state https://github.com/swift-server/async-http-client/issues/638
408407
self.failedConsecutiveConnectionAttempts += 1
409408
self.lastConnectFailure = error
410409

411-
guard self.retryConnectionEstablishment else {
412-
guard let (index, _) = self.connections.failConnection(connectionID) else {
413-
preconditionFailure("A connection attempt failed, that the state machine knows nothing about. Somewhere state was lost.")
410+
switch self.lifecycleState {
411+
case .running:
412+
guard self.retryConnectionEstablishment else {
413+
guard let (index, _) = self.connections.failConnection(connectionID) else {
414+
preconditionFailure("A connection attempt failed, that the state machine knows nothing about. Somewhere state was lost.")
415+
}
416+
self.connections.removeConnection(at: index)
417+
418+
return .init(
419+
request: self.failAllRequests(reason: error),
420+
connection: .none
421+
)
414422
}
415-
self.connections.removeConnection(at: index)
416423

417-
return .init(
418-
request: self.failAllRequests(reason: error),
419-
connection: .none
420-
)
424+
let eventLoop = self.connections.backoffNextConnectionAttempt(connectionID)
425+
let backoff = calculateBackoff(failedAttempt: self.failedConsecutiveConnectionAttempts)
426+
return .init(request: .none, connection: .scheduleBackoffTimer(connectionID, backoff: backoff, on: eventLoop))
427+
case .shuttingDown:
428+
guard let (index, context) = self.connections.failConnection(connectionID) else {
429+
preconditionFailure("A connection attempt failed, that the state machine knows nothing about. Somewhere state was lost.")
430+
}
431+
return self.nextActionForFailedConnection(at: index, on: context.eventLoop)
432+
case .shutDown:
433+
preconditionFailure("If the pool is already shutdown, all connections must have been torn down.")
421434
}
422-
423-
let eventLoop = self.connections.backoffNextConnectionAttempt(connectionID)
424-
let backoff = calculateBackoff(failedAttempt: self.failedConsecutiveConnectionAttempts)
425-
return .init(request: .none, connection: .scheduleBackoffTimer(connectionID, backoff: backoff, on: eventLoop))
426435
}
427436

428437
mutating func waitingForConnectivity(_ error: Error, connectionID: Connection.ID) -> Action {

Diff for: Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP2StateMachineTests+XCTest.swift

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ extension HTTPConnectionPool_HTTP2StateMachineTests {
2727
return [
2828
("testCreatingOfConnection", testCreatingOfConnection),
2929
("testConnectionFailureBackoff", testConnectionFailureBackoff),
30+
("testConnectionFailureWhileShuttingDown", testConnectionFailureWhileShuttingDown),
3031
("testConnectionFailureWithoutRetry", testConnectionFailureWithoutRetry),
3132
("testCancelRequestWorks", testCancelRequestWorks),
3233
("testExecuteOnShuttingDownPool", testExecuteOnShuttingDownPool),

Diff for: Tests/AsyncHTTPClientTests/HTTPConnectionPool+HTTP2StateMachineTests.swift

+38
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,44 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
194194
XCTAssertEqual(state.connectionCreationBackoffDone(newConnectionID), .none)
195195
}
196196

197+
func testConnectionFailureWhileShuttingDown() {
198+
struct SomeError: Error, Equatable {}
199+
let elg = EmbeddedEventLoopGroup(loops: 4)
200+
defer { XCTAssertNoThrow(try elg.syncShutdownGracefully()) }
201+
202+
var state = HTTPConnectionPool.HTTP2StateMachine(
203+
idGenerator: .init(),
204+
retryConnectionEstablishment: false,
205+
lifecycleState: .running
206+
)
207+
208+
let mockRequest = MockHTTPRequest(eventLoop: elg.next())
209+
let request = HTTPConnectionPool.Request(mockRequest)
210+
211+
let action = state.executeRequest(request)
212+
XCTAssertEqual(.scheduleRequestTimeout(for: request, on: mockRequest.eventLoop), action.request)
213+
214+
// 1. connection attempt
215+
guard case .createConnection(let connectionID, on: let connectionEL) = action.connection else {
216+
return XCTFail("Unexpected connection action: \(action.connection)")
217+
}
218+
XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux
219+
220+
// 2. initialise shutdown
221+
let shutdownAction = state.shutdown()
222+
XCTAssertEqual(shutdownAction.connection, .cleanupConnections(.init(), isShutdown: .no))
223+
guard case .failRequestsAndCancelTimeouts(let requestsToFail, let requestError) = shutdownAction.request else {
224+
return XCTFail("Unexpected request action: \(action.request)")
225+
}
226+
XCTAssertEqualTypeAndValue(requestError, HTTPClientError.cancelled)
227+
XCTAssertEqualTypeAndValue(requestsToFail, [request])
228+
229+
// 3. connection attempt fails
230+
let failedConnectAction = state.failedToCreateNewConnection(SomeError(), connectionID: connectionID)
231+
XCTAssertEqual(failedConnectAction.request, .none)
232+
XCTAssertEqual(failedConnectAction.connection, .cleanupConnections(.init(), isShutdown: .yes(unclean: true)))
233+
}
234+
197235
func testConnectionFailureWithoutRetry() {
198236
struct SomeError: Error, Equatable {}
199237
let elg = EmbeddedEventLoopGroup(loops: 4)

0 commit comments

Comments
 (0)