Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,14 @@ final class HTTP1Connection {
self.channel.triggerUserOutboundEvent(HTTPConnectionEvent.shutdownRequested, promise: nil)
}

func close(promise: EventLoopPromise<Void>?) {
return self.channel.close(mode: .all, promise: promise)
}

func close() -> EventLoopFuture<Void> {
return self.channel.close()
let promise = self.channel.eventLoop.makePromise(of: Void.self)
self.close(promise: promise)
return promise.futureResult
}

func taskCompleted() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,14 @@ final class HTTP2Connection {
}
}

func close(promise: EventLoopPromise<Void>?) {
return self.channel.close(mode: .all, promise: promise)
}

func close() -> EventLoopFuture<Void> {
self.channel.close()
let promise = self.channel.eventLoop.makePromise(of: Void.self)
self.close(promise: promise)
return promise.futureResult
}

private func start() -> EventLoopFuture<Void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,157 @@
//
//===----------------------------------------------------------------------===//

import Logging
import NIO
import NIOConcurrencyHelpers
import NIOHTTP1

extension HTTPConnectionPool {
final class Manager {
private typealias Key = ConnectionPool.Key

enum State {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: private

case active
case shuttingDown(promise: EventLoopPromise<Bool>, unclean: Bool)
case shutDown
}

let eventLoopGroup: EventLoopGroup
let configuration: HTTPClient.Configuration
let connectionIDGenerator = Connection.ID.globalGenerator
let logger: Logger
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private?


private var state: State = .active
private var _pools: [Key: HTTPConnectionPool] = [:]
private let lock = Lock()

private let sslContextCache = SSLContextCache()

init(eventLoopGroup: EventLoopGroup,
configuration: HTTPClient.Configuration,
backgroundActivityLogger logger: Logger) {
self.eventLoopGroup = eventLoopGroup
self.configuration = configuration
self.logger = logger
}

deinit {
guard case .shutDown = self.state else {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about shutting down? You could call shutdown and not bother waiting for the future to complete as well, I assume?

preconditionFailure("Manager must be shutdown before deinit")
}
}

func executeRequest(_ request: HTTPSchedulableRequest) {
let poolKey = request.poolKey
let poolResult = self.lock.withLock { () -> Result<HTTPConnectionPool, HTTPClientError> in
switch self.state {
case .active:
if let pool = self._pools[poolKey] {
return .success(pool)
}

let pool = HTTPConnectionPool(
eventLoopGroup: self.eventLoopGroup,
sslContextCache: self.sslContextCache,
tlsConfiguration: request.tlsConfiguration,
clientConfiguration: self.configuration,
key: poolKey,
delegate: self,
idGenerator: self.connectionIDGenerator,
backgroundActivityLogger: self.logger
)
self._pools[poolKey] = pool
return .success(pool)

case .shuttingDown, .shutDown:
return .failure(HTTPClientError.alreadyShutdown)
}
}

switch poolResult {
case .success(let pool):
pool.executeRequest(request)
case .failure(let error):
request.fail(error)
}
}

func shutdown(on eventLoop: EventLoop) -> EventLoopFuture<Bool> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the Bool here?

enum ShutdownAction {
case done(EventLoopFuture<Bool>)
case shutdown([Key: HTTPConnectionPool], EventLoopFuture<Bool>)
}

let action = self.lock.withLock { () -> ShutdownAction in
switch self.state {
case .active:
// If there aren't any pools, we can mark the pool as shut down right away.
if self._pools.isEmpty {
let future = eventLoop.makeSucceededFuture(true)
self.state = .shutDown
return .done(future)
} else {
let promise = eventLoop.makePromise(of: Bool.self)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth a comment here to indicate when this promise is completed.

self.state = .shuttingDown(promise: promise, unclean: false)
return .shutdown(self._pools, promise.futureResult)
}

case .shuttingDown, .shutDown:
preconditionFailure("PoolManager already shutdown")
}
}

// if no pools are returned, the manager is already shutdown completely. Inform the
// delegate. This is a very clean shutdown...
switch action {
case .done(let future):
return future

case .shutdown(let pools, let future):
pools.values.forEach { pool in
pool.shutdown()
}
return future
}
}
}
}

extension HTTPConnectionPool.Manager: HTTPConnectionPoolDelegate {
func connectionPoolDidShutdown(_ pool: HTTPConnectionPool, unclean: Bool) {
enum CloseAction {
case close(EventLoopPromise<Bool>, unclean: Bool)
case wait
}

let closeAction = self.lock.withLock { () -> CloseAction in
switch self.state {
case .active, .shutDown:
preconditionFailure("Why are pools shutting down, if the manager did not give a signal")

case .shuttingDown(let promise, let soFarUnclean):
guard self._pools.removeValue(forKey: pool.key) === pool else {
preconditionFailure("Expected that the pool was ")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That suspense is killing me!

}

if self._pools.isEmpty {
self.state = .shutDown
return .close(promise, unclean: soFarUnclean || unclean)
} else {
self.state = .shuttingDown(promise: promise, unclean: soFarUnclean || unclean)
return .wait
}
}
}

switch closeAction {
case .close(let promise, unclean: let unclean):
promise.succeed(unclean)
case .wait:
break
}
}
}

extension HTTPConnectionPool.Connection.ID {
static var globalGenerator = Generator()
Expand Down
Loading