Skip to content

Commit 513be15

Browse files
authored
EventLoop preference overhaul (#102)
1 parent 79cd718 commit 513be15

File tree

7 files changed

+368
-97
lines changed

7 files changed

+368
-97
lines changed

Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ let package = Package(
2121
.library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]),
2222
],
2323
dependencies: [
24-
.package(url: "https://github.com/apple/swift-nio.git", from: "2.0.0"),
24+
.package(url: "https://github.com/apple/swift-nio.git", from: "2.8.0"),
2525
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.0.0"),
2626
],
2727
targets: [

Sources/AsyncHTTPClient/HTTPClient.swift

+48-13
Original file line numberDiff line numberDiff line change
@@ -205,28 +205,42 @@ public class HTTPClient {
205205
switch eventLoop.preference {
206206
case .indifferent:
207207
return self.execute(request: request, delegate: delegate, eventLoop: self.eventLoopGroup.next(), deadline: deadline)
208-
case .prefers(let preferred):
209-
precondition(self.eventLoopGroup.makeIterator().contains { $0 === preferred }, "Provided EventLoop must be part of clients EventLoopGroup.")
210-
return self.execute(request: request, delegate: delegate, eventLoop: preferred, deadline: deadline)
208+
case .delegate(on: let eventLoop):
209+
precondition(self.eventLoopGroup.makeIterator().contains { $0 === eventLoop }, "Provided EventLoop must be part of clients EventLoopGroup.")
210+
return self.execute(request: request, delegate: delegate, eventLoop: eventLoop, deadline: deadline)
211+
case .delegateAndChannel(on: let eventLoop):
212+
precondition(self.eventLoopGroup.makeIterator().contains { $0 === eventLoop }, "Provided EventLoop must be part of clients EventLoopGroup.")
213+
return self.execute(request: request, delegate: delegate, eventLoop: eventLoop, deadline: deadline)
214+
case .testOnly_exact(channelOn: let channelEL, delegateOn: let delegateEL):
215+
return self.execute(request: request,
216+
delegate: delegate,
217+
eventLoop: delegateEL,
218+
channelEL: channelEL,
219+
deadline: deadline)
211220
}
212221
}
213222

214223
private func execute<Delegate: HTTPClientResponseDelegate>(request: Request,
215224
delegate: Delegate,
216-
eventLoop: EventLoop,
225+
eventLoop delegateEL: EventLoop,
226+
channelEL: EventLoop? = nil,
217227
deadline: NIODeadline? = nil) -> Task<Delegate.Response> {
218228
let redirectHandler: RedirectHandler<Delegate.Response>?
219229
if self.configuration.followRedirects {
220230
redirectHandler = RedirectHandler<Delegate.Response>(request: request) { newRequest in
221-
self.execute(request: newRequest, delegate: delegate, eventLoop: eventLoop, deadline: deadline)
231+
self.execute(request: newRequest,
232+
delegate: delegate,
233+
eventLoop: delegateEL,
234+
channelEL: channelEL,
235+
deadline: deadline)
222236
}
223237
} else {
224238
redirectHandler = nil
225239
}
226240

227-
let task = Task<Delegate.Response>(eventLoop: eventLoop)
241+
let task = Task<Delegate.Response>(eventLoop: delegateEL)
228242

229-
var bootstrap = ClientBootstrap(group: eventLoop)
243+
var bootstrap = ClientBootstrap(group: channelEL ?? delegateEL)
230244
.channelOption(ChannelOptions.socket(SocketOptionLevel(IPPROTO_TCP), TCP_NODELAY), value: 1)
231245
.channelInitializer { channel in
232246
let encoder = HTTPRequestEncoder()
@@ -262,9 +276,7 @@ public class HTTPClient {
262276
.flatMap { channel in
263277
channel.writeAndFlush(request)
264278
}
265-
.whenFailure { error in
266-
task.fail(error)
267-
}
279+
.cascadeFailure(to: task.promise)
268280

269281
return task
270282
}
@@ -351,8 +363,12 @@ public class HTTPClient {
351363
enum Preference {
352364
/// Event Loop will be selected by the library.
353365
case indifferent
354-
/// Library will try to use provided event loop if possible.
355-
case prefers(EventLoop)
366+
/// The delegate will be run on the specified EventLoop (and the Channel if possible).
367+
case delegate(on: EventLoop)
368+
/// The delegate and the `Channel` will be run on the specified EventLoop.
369+
case delegateAndChannel(on: EventLoop)
370+
371+
case testOnly_exact(channelOn: EventLoop, delegateOn: EventLoop)
356372
}
357373

358374
var preference: Preference
@@ -363,9 +379,28 @@ public class HTTPClient {
363379

364380
/// Event Loop will be selected by the library.
365381
public static let indifferent = EventLoopPreference(.indifferent)
382+
366383
/// Library will try to use provided event loop if possible.
384+
@available(*, deprecated, renamed: "delegate(on:)")
367385
public static func prefers(_ eventLoop: EventLoop) -> EventLoopPreference {
368-
return EventLoopPreference(.prefers(eventLoop))
386+
return EventLoopPreference(.delegate(on: eventLoop))
387+
}
388+
389+
/// The delegate will be run on the specified EventLoop (and the Channel if possible).
390+
///
391+
/// This will call the configured delegate on `eventLoop` and will try to use a `Channel` on the same
392+
/// `EventLoop` but will not establish a new network connection just to satisfy the `EventLoop` preference if
393+
/// another existing connection on a different `EventLoop` is readily available from a connection pool.
394+
public static func delegate(on eventLoop: EventLoop) -> EventLoopPreference {
395+
return EventLoopPreference(.delegate(on: eventLoop))
396+
}
397+
398+
/// The delegate and the `Channel` will be run on the specified EventLoop.
399+
///
400+
/// Use this for use-cases where you prefer a new connection to be established over re-using an existing
401+
/// connection that might be on a different `EventLoop`.
402+
public static func delegateAndChannel(on eventLoop: EventLoop) -> EventLoopPreference {
403+
return EventLoopPreference(.delegateAndChannel(on: eventLoop))
369404
}
370405
}
371406
}

0 commit comments

Comments
 (0)