Skip to content

Commit 5cefa51

Browse files
fabianfettglbrntt
andcommitted
Code review
Co-authored-by: George Barnett <[email protected]>
1 parent 8e4093c commit 5cefa51

File tree

6 files changed

+57
-40
lines changed

6 files changed

+57
-40
lines changed

Diff for: Sources/AsyncHTTPClient/ConnectionPool/HTTP1.1/HTTP1Connection.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ final class HTTP1Connection {
7979
self.channel.triggerUserOutboundEvent(HTTPConnectionEvent.shutdownRequested, promise: nil)
8080
}
8181

82-
func close() -> EventLoopFuture<Void> {
83-
return self.channel.close()
82+
func close(promise: EventLoopPromise<Void>?) {
83+
return self.channel.close(mode: .all, promise: promise)
8484
}
8585

8686
func taskCompleted() {

Diff for: Sources/AsyncHTTPClient/ConnectionPool/HTTP2/HTTP2Connection.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ final class HTTP2Connection {
144144
}
145145
}
146146

147-
func close() -> EventLoopFuture<Void> {
148-
self.channel.close()
147+
func close(promise: EventLoopPromise<Void>?) {
148+
return self.channel.close(mode: .all, promise: promise)
149149
}
150150

151151
private func start() -> EventLoopFuture<Void> {

Diff for: Sources/AsyncHTTPClient/ConnectionPool/HTTPConnectionPool.swift

+44-33
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ protocol HTTPConnectionPoolDelegate {
2121
func connectionPoolDidShutdown(_ pool: HTTPConnectionPool, unclean: Bool)
2222
}
2323

24-
class HTTPConnectionPool {
24+
final class HTTPConnectionPool {
2525
struct Connection: Hashable {
2626
typealias ID = Int
2727

@@ -92,14 +92,14 @@ class HTTPConnectionPool {
9292

9393
/// Closes the connection without cancelling running requests. Use this when you are sure, that the
9494
/// connection is currently idle.
95-
fileprivate func close() -> EventLoopFuture<Void> {
95+
fileprivate func close(promise: EventLoopPromise<Void>?) {
9696
switch self._ref {
9797
case .http1_1(let connection):
98-
return connection.close()
98+
return connection.close(promise: promise)
9999
case .http2(let connection):
100-
return connection.close()
101-
case .__testOnly_connection(_, let eventLoop):
102-
return eventLoop.makeSucceededFuture(())
100+
return connection.close(promise: promise)
101+
case .__testOnly_connection:
102+
promise?.succeed(())
103103
}
104104
}
105105

@@ -139,18 +139,20 @@ class HTTPConnectionPool {
139139
}
140140
}
141141

142-
let timerLock = Lock()
142+
static let fallbackConnectTimeout: TimeAmount = .seconds(30)
143+
144+
private let timerLock = Lock()
143145
private var _requestTimer = [Request.ID: Scheduled<Void>]()
144146
private var _idleTimer = [Connection.ID: Scheduled<Void>]()
145147
private var _backoffTimer = [Connection.ID: Scheduled<Void>]()
146148

147-
let key: ConnectionPool.Key
148-
var logger: Logger
149+
private let key: ConnectionPool.Key
150+
private var logger: Logger
149151

150-
let eventLoopGroup: EventLoopGroup
151-
let connectionFactory: ConnectionFactory
152-
let clientConfiguration: HTTPClient.Configuration
153-
let idleConnectionTimeout: TimeAmount
152+
private let eventLoopGroup: EventLoopGroup
153+
private let connectionFactory: ConnectionFactory
154+
private let clientConfiguration: HTTPClient.Configuration
155+
private let idleConnectionTimeout: TimeAmount
154156

155157
let delegate: HTTPConnectionPoolDelegate
156158

@@ -197,12 +199,14 @@ class HTTPConnectionPool {
197199
self.run(action: action)
198200
}
199201

200-
func run(action: StateMachine.Action) {
202+
// MARK: Run actions
203+
204+
private func run(action: StateMachine.Action) {
201205
self.runConnectionAction(action.connection)
202206
self.runRequestAction(action.request)
203207
}
204208

205-
func runConnectionAction(_ action: StateMachine.ConnectionAction) {
209+
private func runConnectionAction(_ action: StateMachine.ConnectionAction) {
206210
switch action {
207211
case .createConnection(let connectionID, let eventLoop):
208212
self.createConnection(connectionID, on: eventLoop)
@@ -218,19 +222,19 @@ class HTTPConnectionPool {
218222

219223
case .closeConnection(let connection, isShutdown: let isShutdown):
220224
// we are not interested in the close future...
221-
_ = connection.close()
225+
connection.close(promise: nil)
222226

223227
if case .yes(let unclean) = isShutdown {
224228
self.delegate.connectionPoolDidShutdown(self, unclean: unclean)
225229
}
226230

227231
case .cleanupConnections(let cleanupContext, isShutdown: let isShutdown):
228232
for connection in cleanupContext.close {
229-
_ = connection.close()
233+
connection.close(promise: nil)
230234
}
231235

232236
for connection in cleanupContext.cancel {
233-
_ = connection.close()
237+
connection.close(promise: nil)
234238
}
235239

236240
for connectionID in cleanupContext.connectBackoff {
@@ -246,7 +250,11 @@ class HTTPConnectionPool {
246250
}
247251
}
248252

249-
func runRequestAction(_ action: StateMachine.RequestAction) {
253+
private func runRequestAction(_ action: StateMachine.RequestAction) {
254+
// The order of execution fail/execute request vs cancelling the request timeout timer does
255+
// not matter in the actions here. The actions don't cause any side effects that will be
256+
// reported back to the state machine and are not dependent on each other.
257+
250258
switch action {
251259
case .executeRequest(let request, let connection, cancelTimeout: let cancelTimeout):
252260
connection.executeRequest(request.req)
@@ -255,10 +263,8 @@ class HTTPConnectionPool {
255263
}
256264

257265
case .executeRequestsAndCancelTimeouts(let requests, let connection):
258-
for request in requests {
259-
connection.executeRequest(request.req)
260-
self.cancelRequestTimeout(request.id)
261-
}
266+
requests.forEach { connection.executeRequest($0.req) }
267+
self.cancelRequestTimeouts(requests)
262268

263269
case .failRequest(let request, let error, cancelTimeout: let cancelTimeout):
264270
if cancelTimeout {
@@ -267,10 +273,8 @@ class HTTPConnectionPool {
267273
request.req.fail(error)
268274

269275
case .failRequestsAndCancelTimeouts(let requests, let error):
270-
for request in requests {
271-
self.cancelRequestTimeout(request.id)
272-
request.req.fail(error)
273-
}
276+
requests.forEach { $0.req.fail(error) }
277+
self.cancelRequestTimeouts(requests)
274278

275279
case .scheduleRequestTimeout(let request, on: let eventLoop):
276280
self.scheduleRequestTimeout(request, on: eventLoop)
@@ -283,15 +287,13 @@ class HTTPConnectionPool {
283287
}
284288
}
285289

286-
// MARK: Run actions
287-
288290
private func createConnection(_ connectionID: Connection.ID, on eventLoop: EventLoop) {
289291
self.connectionFactory.makeConnection(
290292
for: self,
291293
connectionID: connectionID,
292294
http1ConnectionDelegate: self,
293295
http2ConnectionDelegate: self,
294-
deadline: .now() + (self.clientConfiguration.timeout.connect ?? .seconds(30)),
296+
deadline: .now() + (self.clientConfiguration.timeout.connect ?? Self.fallbackConnectTimeout),
295297
eventLoop: eventLoop,
296298
logger: self.logger
297299
)
@@ -303,17 +305,17 @@ class HTTPConnectionPool {
303305
// The timer has fired. Now we need to do a couple of things:
304306
//
305307
// 1. Remove ourselves from the timer dictionary to not leak any data. If our
306-
// waiter entry still exist, we need to tell the state machine, that we want
308+
// waiter entry still exists, we need to tell the state machine, that we want
307309
// to fail the request.
308310

309-
let timeout = self.timerLock.withLock {
311+
let timeoutFired = self.timerLock.withLock {
310312
self._requestTimer.removeValue(forKey: requestID) != nil
311313
}
312314

313315
// 2. If the entry did not exists anymore, we can assume that the request was
314316
// scheduled on another connection. The timer still fired anyhow because of a
315317
// race. In such a situation we don't need to do anything.
316-
guard timeout else { return }
318+
guard timeoutFired else { return }
317319

318320
// 3. Tell the state machine about the timeout
319321
let action = self.stateLock.withLock {
@@ -339,6 +341,15 @@ class HTTPConnectionPool {
339341
scheduled?.cancel()
340342
}
341343

344+
private func cancelRequestTimeouts(_ requests: [Request]) {
345+
let scheduled = self.timerLock.withLock {
346+
requests.compactMap {
347+
self._requestTimer.removeValue(forKey: $0.id)
348+
}
349+
}
350+
scheduled.forEach { $0.cancel() }
351+
}
352+
342353
private func scheduleIdleTimerForConnection(_ connectionID: Connection.ID, on eventLoop: EventLoop) {
343354
let scheduled = eventLoop.scheduleTask(in: self.idleConnectionTimeout) {
344355
// there might be a race between a cancelTimer call and the triggering

Diff for: Sources/AsyncHTTPClient/HTTPClient.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,7 @@ public class HTTPClient {
829829
extension HTTPClient.Configuration {
830830
/// Timeout configuration.
831831
public struct Timeout {
832-
/// Specifies connect timeout.
832+
/// Specifies connect timeout. If no connect timeout is given, a default 30 seconds timeout will applied.
833833
public var connect: TimeAmount?
834834
/// Specifies read timeout.
835835
public var read: TimeAmount?

Diff for: Tests/AsyncHTTPClientTests/HTTP1ConnectionTests.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ class HTTP1ConnectionTests: XCTestCase {
4242
XCTAssertNotNil(try embedded.pipeline.syncOperations.handler(type: ByteToMessageHandler<HTTPResponseDecoder>.self))
4343
XCTAssertNotNil(try embedded.pipeline.syncOperations.handler(type: NIOHTTPResponseDecompressor.self))
4444

45-
XCTAssertNoThrow(try connection?.close().wait())
45+
let promise = embedded.eventLoop.makePromise(of: Void.self)
46+
connection?.close(promise: promise)
47+
XCTAssertNoThrow(try promise.futureResult.wait())
4648
embedded.embeddedEventLoop.run()
4749
XCTAssert(!embedded.isActive)
4850
}

Diff for: Tests/AsyncHTTPClientTests/HTTP2ConnectionTests.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,11 @@ class HTTP2ConnectionTests: XCTestCase {
117117
guard let http2Connection = maybeHTTP2Connection else {
118118
return XCTFail("Expected to have an HTTP2 connection here.")
119119
}
120-
defer { XCTAssertNoThrow(try http2Connection.close().wait()) }
120+
defer {
121+
let promise = eventLoop.makePromise(of: Void.self)
122+
http2Connection.close(promise: promise)
123+
XCTAssertNoThrow(try promise.futureResult.wait())
124+
}
121125

122126
var futures = [EventLoopFuture<HTTPClient.Response>]()
123127

0 commit comments

Comments
 (0)