Skip to content

Commit

Permalink
Switch over state in `HTTPConnectionPool.HTTP2StateMachine.failedToCr…
Browse files Browse the repository at this point in the history
…eateNewConnection` (#647)
  • Loading branch information
dnadoba authored Nov 9, 2022
1 parent fd03ed0 commit 5bee16a
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -404,25 +404,34 @@ extension HTTPConnectionPool {
}

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

guard self.retryConnectionEstablishment else {
guard let (index, _) = self.connections.failConnection(connectionID) else {
preconditionFailure("A connection attempt failed, that the state machine knows nothing about. Somewhere state was lost.")
switch self.lifecycleState {
case .running:
guard self.retryConnectionEstablishment else {
guard let (index, _) = self.connections.failConnection(connectionID) else {
preconditionFailure("A connection attempt failed, that the state machine knows nothing about. Somewhere state was lost.")
}
self.connections.removeConnection(at: index)

return .init(
request: self.failAllRequests(reason: error),
connection: .none
)
}
self.connections.removeConnection(at: index)

return .init(
request: self.failAllRequests(reason: error),
connection: .none
)
let eventLoop = self.connections.backoffNextConnectionAttempt(connectionID)
let backoff = calculateBackoff(failedAttempt: self.failedConsecutiveConnectionAttempts)
return .init(request: .none, connection: .scheduleBackoffTimer(connectionID, backoff: backoff, on: eventLoop))
case .shuttingDown:
guard let (index, context) = self.connections.failConnection(connectionID) else {
preconditionFailure("A connection attempt failed, that the state machine knows nothing about. Somewhere state was lost.")
}
return self.nextActionForFailedConnection(at: index, on: context.eventLoop)
case .shutDown:
preconditionFailure("If the pool is already shutdown, all connections must have been torn down.")
}

let eventLoop = self.connections.backoffNextConnectionAttempt(connectionID)
let backoff = calculateBackoff(failedAttempt: self.failedConsecutiveConnectionAttempts)
return .init(request: .none, connection: .scheduleBackoffTimer(connectionID, backoff: backoff, on: eventLoop))
}

mutating func waitingForConnectivity(_ error: Error, connectionID: Connection.ID) -> Action {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ extension HTTPConnectionPool_HTTP2StateMachineTests {
return [
("testCreatingOfConnection", testCreatingOfConnection),
("testConnectionFailureBackoff", testConnectionFailureBackoff),
("testConnectionFailureWhileShuttingDown", testConnectionFailureWhileShuttingDown),
("testConnectionFailureWithoutRetry", testConnectionFailureWithoutRetry),
("testCancelRequestWorks", testCancelRequestWorks),
("testExecuteOnShuttingDownPool", testExecuteOnShuttingDownPool),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,44 @@ class HTTPConnectionPool_HTTP2StateMachineTests: XCTestCase {
XCTAssertEqual(state.connectionCreationBackoffDone(newConnectionID), .none)
}

func testConnectionFailureWhileShuttingDown() {
struct SomeError: Error, Equatable {}
let elg = EmbeddedEventLoopGroup(loops: 4)
defer { XCTAssertNoThrow(try elg.syncShutdownGracefully()) }

var state = HTTPConnectionPool.HTTP2StateMachine(
idGenerator: .init(),
retryConnectionEstablishment: false,
lifecycleState: .running
)

let mockRequest = MockHTTPRequest(eventLoop: elg.next())
let request = HTTPConnectionPool.Request(mockRequest)

let action = state.executeRequest(request)
XCTAssertEqual(.scheduleRequestTimeout(for: request, on: mockRequest.eventLoop), action.request)

// 1. connection attempt
guard case .createConnection(let connectionID, on: let connectionEL) = action.connection else {
return XCTFail("Unexpected connection action: \(action.connection)")
}
XCTAssert(connectionEL === mockRequest.eventLoop) // XCTAssertIdentical not available on Linux

// 2. initialise shutdown
let shutdownAction = state.shutdown()
XCTAssertEqual(shutdownAction.connection, .cleanupConnections(.init(), isShutdown: .no))
guard case .failRequestsAndCancelTimeouts(let requestsToFail, let requestError) = shutdownAction.request else {
return XCTFail("Unexpected request action: \(action.request)")
}
XCTAssertEqualTypeAndValue(requestError, HTTPClientError.cancelled)
XCTAssertEqualTypeAndValue(requestsToFail, [request])

// 3. connection attempt fails
let failedConnectAction = state.failedToCreateNewConnection(SomeError(), connectionID: connectionID)
XCTAssertEqual(failedConnectAction.request, .none)
XCTAssertEqual(failedConnectAction.connection, .cleanupConnections(.init(), isShutdown: .yes(unclean: true)))
}

func testConnectionFailureWithoutRetry() {
struct SomeError: Error, Equatable {}
let elg = EmbeddedEventLoopGroup(loops: 4)
Expand Down

0 comments on commit 5bee16a

Please sign in to comment.