Files
async-http-client/Sources/AsyncHTTPClient/ConnectionPool/State Machine/HTTPConnectionPool+StateMachine.swift
T
2021-09-09 15:34:03 +02:00

221 lines
7.9 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the AsyncHTTPClient open source project
//
// Copyright (c) 2021 Apple Inc. and the AsyncHTTPClient project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import NIO
import NIOHTTP1
extension HTTPConnectionPool {
struct StateMachine {
struct Action {
let request: RequestAction
let connection: ConnectionAction
init(request: RequestAction, connection: ConnectionAction) {
self.request = request
self.connection = connection
}
static let none: Action = Action(request: .none, connection: .none)
}
enum ConnectionAction {
enum IsShutdown: Equatable {
case yes(unclean: Bool)
case no
}
case createConnection(Connection.ID, on: EventLoop)
case scheduleBackoffTimer(Connection.ID, backoff: TimeAmount, on: EventLoop)
case scheduleTimeoutTimer(Connection.ID, on: EventLoop)
case cancelTimeoutTimer(Connection.ID)
case closeConnection(Connection, isShutdown: IsShutdown)
case cleanupConnections(CleanupContext, isShutdown: IsShutdown)
case none
}
enum RequestAction {
case executeRequest(Request, Connection, cancelTimeout: Bool)
case executeRequestsAndCancelTimeouts([Request], Connection)
case failRequest(Request, Error, cancelTimeout: Bool)
case failRequestsAndCancelTimeouts([Request], Error)
case scheduleRequestTimeout(for: Request, on: EventLoop)
case cancelRequestTimeout(Request.ID)
case none
}
enum HTTPVersionState {
case http1(HTTP1StateMachine)
}
var state: HTTPVersionState
var isShuttingDown: Bool = false
let eventLoopGroup: EventLoopGroup
let maximumConcurrentHTTP1Connections: Int
init(eventLoopGroup: EventLoopGroup, idGenerator: Connection.ID.Generator, maximumConcurrentHTTP1Connections: Int) {
self.maximumConcurrentHTTP1Connections = maximumConcurrentHTTP1Connections
let http1State = HTTP1StateMachine(
idGenerator: idGenerator,
maximumConcurrentConnections: maximumConcurrentHTTP1Connections
)
self.state = .http1(http1State)
self.eventLoopGroup = eventLoopGroup
}
mutating func executeRequest(_ request: Request) -> Action {
switch self.state {
case .http1(var http1StateMachine):
let action = http1StateMachine.executeRequest(request)
self.state = .http1(http1StateMachine)
return action
}
}
mutating func newHTTP1ConnectionCreated(_ connection: Connection) -> Action {
switch self.state {
case .http1(var http1StateMachine):
let action = http1StateMachine.newHTTP1ConnectionEstablished(connection)
self.state = .http1(http1StateMachine)
return action
}
}
mutating func failedToCreateNewConnection(_ error: Error, connectionID: Connection.ID) -> Action {
switch self.state {
case .http1(var http1StateMachine):
let action = http1StateMachine.failedToCreateNewConnection(
error,
connectionID: connectionID
)
self.state = .http1(http1StateMachine)
return action
}
}
mutating func connectionCreationBackoffDone(_ connectionID: Connection.ID) -> Action {
switch self.state {
case .http1(var http1StateMachine):
let action = http1StateMachine.connectionCreationBackoffDone(connectionID)
self.state = .http1(http1StateMachine)
return action
}
}
/// A request has timed out.
///
/// This is different to a request being cancelled. If a request times out, we need to fail the
/// request, but don't need to cancel the timer (it already triggered). If a request is cancelled
/// we don't need to fail it but we need to cancel its timeout timer.
mutating func timeoutRequest(_ requestID: Request.ID) -> Action {
switch self.state {
case .http1(var http1StateMachine):
let action = http1StateMachine.timeoutRequest(requestID)
self.state = .http1(http1StateMachine)
return action
}
}
/// A request was cancelled.
///
/// This is different to a request timing out. If a request is cancelled we don't need to fail it but we
/// need to cancel its timeout timer. If a request times out, we need to fail the request, but don't
/// need to cancel the timer (it already triggered).
mutating func cancelRequest(_ requestID: Request.ID) -> Action {
switch self.state {
case .http1(var http1StateMachine):
let action = http1StateMachine.cancelRequest(requestID)
self.state = .http1(http1StateMachine)
return action
}
}
mutating func connectionIdleTimeout(_ connectionID: Connection.ID) -> Action {
switch self.state {
case .http1(var http1StateMachine):
let action = http1StateMachine.connectionIdleTimeout(connectionID)
self.state = .http1(http1StateMachine)
return action
}
}
/// A connection has been closed
mutating func connectionClosed(_ connectionID: Connection.ID) -> Action {
switch self.state {
case .http1(var http1StateMachine):
let action = http1StateMachine.connectionClosed(connectionID)
self.state = .http1(http1StateMachine)
return action
}
}
mutating func http1ConnectionReleased(_ connectionID: Connection.ID) -> Action {
switch self.state {
case .http1(var http1StateMachine):
let action = http1StateMachine.http1ConnectionReleased(connectionID)
self.state = .http1(http1StateMachine)
return action
}
}
mutating func shutdown() -> Action {
precondition(!self.isShuttingDown, "Shutdown must only be called once")
self.isShuttingDown = true
switch self.state {
case .http1(var http1StateMachine):
let action = http1StateMachine.shutdown()
self.state = .http1(http1StateMachine)
return action
}
}
}
}
extension HTTPConnectionPool {
/// The pool cleanup todo list.
struct CleanupContext: Equatable {
/// the connections to close right away. These are idle.
var close: [Connection]
/// the connections that currently run a request that needs to be cancelled to close the connections
var cancel: [Connection]
/// the connections that are backing off from connection creation
var connectBackoff: [Connection.ID]
init(close: [Connection] = [], cancel: [Connection] = [], connectBackoff: [Connection.ID] = []) {
self.close = close
self.cancel = cancel
self.connectBackoff = connectBackoff
}
}
}
extension HTTPConnectionPool.StateMachine: CustomStringConvertible {
var description: String {
switch self.state {
case .http1(let http1):
return ".http1(\(http1))"
}
}
}