Skip to content

Commit ac0b9f7

Browse files
committed
User bigger thread pool and initlize lazily during first file write
1 parent 1afc151 commit ac0b9f7

File tree

4 files changed

+67
-26
lines changed

4 files changed

+67
-26
lines changed

Sources/AsyncHTTPClient/FileDownloadDelegate.swift

+10-4
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate {
8383
self.io = NonBlockingFileIO(threadPool: pool)
8484
} else {
8585
// we should use the shared thread pool from the HTTPClient which
86-
// we will get shortly through a call to provideSharedThreadPool(fileIOPool:)
86+
// we will get from the `HTTPClient.Task`
8787
self.io = nil
8888
}
8989

@@ -119,9 +119,15 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate {
119119
task: HTTPClient.Task<Response>,
120120
_ buffer: ByteBuffer
121121
) -> EventLoopFuture<Void> {
122-
guard let io = io else {
123-
preconditionFailure("thread pool not provided by HTTPClient before calling \(#function)")
124-
}
122+
let io: NonBlockingFileIO = {
123+
guard let io = self.io else {
124+
let pool = task.fileIOThreadPool
125+
let io = NonBlockingFileIO(threadPool: pool)
126+
self.io = io
127+
return io
128+
}
129+
return io
130+
}()
125131
self.progress.receivedBytes += buffer.readableBytes
126132
self.reportProgress?(self.progress)
127133

Sources/AsyncHTTPClient/HTTPClient.swift

+29-7
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ public class HTTPClient {
7474
let poolManager: HTTPConnectionPool.Manager
7575

7676
/// Shared thread pool used for file IO. It is given to the user through ``HTTPClientResponseDelegate/provideSharedThreadPool(fileIOPool:)-6phmu``
77-
private let fileIOThreadPool = NIOThreadPool(numberOfThreads: 1)
77+
private var fileIOThreadPool: NIOThreadPool?
78+
private var fileIOThreadPoolLock = Lock()
79+
7880
private var state: State
7981
private let stateLock = Lock()
8082

@@ -100,7 +102,6 @@ public class HTTPClient {
100102
public required init(eventLoopGroupProvider: EventLoopGroupProvider,
101103
configuration: Configuration = Configuration(),
102104
backgroundActivityLogger: Logger) {
103-
self.fileIOThreadPool.start()
104105
self.eventLoopGroupProvider = eventLoopGroupProvider
105106
switch self.eventLoopGroupProvider {
106107
case .shared(let group):
@@ -217,6 +218,16 @@ public class HTTPClient {
217218
}
218219
}
219220

221+
private func shutdownFileIOThreadPool(queue: DispatchQueue, _ callback: @escaping (Error?) -> Void) {
222+
self.fileIOThreadPoolLock.withLockVoid {
223+
guard let fileIOThreadPool = fileIOThreadPool else {
224+
callback(nil)
225+
return
226+
}
227+
fileIOThreadPool.shutdownGracefully(queue: queue, callback)
228+
}
229+
}
230+
220231
private func shutdown(requiresCleanClose: Bool, queue: DispatchQueue, _ callback: @escaping (Error?) -> Void) {
221232
do {
222233
try self.stateLock.withLock {
@@ -245,7 +256,7 @@ public class HTTPClient {
245256
let error: Error? = (requiresClean && unclean) ? HTTPClientError.uncleanShutdown : nil
246257
return (callback, error)
247258
}
248-
self.fileIOThreadPool.shutdownGracefully(queue: queue) { ioThreadPoolError in
259+
self.shutdownFileIOThreadPool(queue: queue) { ioThreadPoolError in
249260
self.shutdownEventLoop(queue: queue) { error in
250261
let reportedError = error ?? ioThreadPoolError ?? uncleanError
251262
callback(reportedError)
@@ -255,6 +266,18 @@ public class HTTPClient {
255266
}
256267
}
257268

269+
private func makeOrGetFileIOThreadPool() -> NIOThreadPool {
270+
self.fileIOThreadPoolLock.withLock {
271+
guard let fileIOThreadPool = fileIOThreadPool else {
272+
let fileIOThreadPool = NIOThreadPool(numberOfThreads: ProcessInfo.processInfo.processorCount)
273+
fileIOThreadPool.start()
274+
self.fileIOThreadPool = fileIOThreadPool
275+
return fileIOThreadPool
276+
}
277+
return fileIOThreadPool
278+
}
279+
}
280+
258281
/// Execute `GET` request using specified URL.
259282
///
260283
/// - parameters:
@@ -572,8 +595,6 @@ public class HTTPClient {
572595
metadata: ["ahc-eventloop": "\(taskEL)",
573596
"ahc-el-preference": "\(eventLoopPreference)"])
574597

575-
delegate.provideSharedThreadPool(fileIOPool: self.fileIOThreadPool)
576-
577598
let failedTask: Task<Delegate.Response>? = self.stateLock.withLock {
578599
switch state {
579600
case .upAndRunning:
@@ -582,7 +603,8 @@ public class HTTPClient {
582603
logger.debug("client is shutting down, failing request")
583604
return Task<Delegate.Response>.failedTask(eventLoop: taskEL,
584605
error: HTTPClientError.alreadyShutdown,
585-
logger: logger)
606+
logger: logger,
607+
makeOrGetFileIOThreadPool: self.makeOrGetFileIOThreadPool)
586608
}
587609
}
588610

@@ -605,7 +627,7 @@ public class HTTPClient {
605627
}
606628
}()
607629

608-
let task = Task<Delegate.Response>(eventLoop: taskEL, logger: logger)
630+
let task = Task<Delegate.Response>(eventLoop: taskEL, logger: logger, makeOrGetFileIOThreadPool: self.makeOrGetFileIOThreadPool)
609631
do {
610632
let requestBag = try RequestBag(
611633
request: request,

Sources/AsyncHTTPClient/HTTPHandler.swift

+14-15
Original file line numberDiff line numberDiff line change
@@ -432,13 +432,6 @@ public class ResponseAccumulator: HTTPClientResponseDelegate {
432432
public protocol HTTPClientResponseDelegate: AnyObject {
433433
associatedtype Response
434434

435-
/// Called exactly once before any other methods of this delegate are called.
436-
/// It is used to give access to the shared thread pool of the ``HTTPClient`` the request is executed on.
437-
/// Use this thread pool to do file IO associated with the request e.g. to save a response to disk.
438-
///
439-
/// - Parameter fileIOPool: File IO Pool
440-
func provideSharedThreadPool(fileIOPool: NIOThreadPool)
441-
442435
/// Called when the request head is sent. Will be called once.
443436
///
444437
/// - parameters:
@@ -510,11 +503,6 @@ public protocol HTTPClientResponseDelegate: AnyObject {
510503
}
511504

512505
extension HTTPClientResponseDelegate {
513-
/// Default implementation of ``HTTPClientResponseDelegate/provideSharedThreadPool(fileIOPool:)-8y1b``
514-
///
515-
/// By default, this does nothing.
516-
public func provideSharedThreadPool(fileIOPool: NIOThreadPool) {}
517-
518506
/// Default implementation of ``HTTPClientResponseDelegate/didSendRequest(task:)-9od5p``.
519507
///
520508
/// By default, this does nothing.
@@ -635,15 +623,26 @@ extension HTTPClient {
635623
private var _isCancelled: Bool = false
636624
private var _taskDelegate: HTTPClientTaskDelegate?
637625
private let lock = Lock()
626+
private let makeOrGetFileIOThreadPool: () -> NIOThreadPool
627+
628+
public var fileIOThreadPool: NIOThreadPool {
629+
self.makeOrGetFileIOThreadPool()
630+
}
638631

639-
init(eventLoop: EventLoop, logger: Logger) {
632+
init(eventLoop: EventLoop, logger: Logger, makeOrGetFileIOThreadPool: @escaping () -> NIOThreadPool) {
640633
self.eventLoop = eventLoop
641634
self.promise = eventLoop.makePromise()
642635
self.logger = logger
636+
self.makeOrGetFileIOThreadPool = makeOrGetFileIOThreadPool
643637
}
644638

645-
static func failedTask(eventLoop: EventLoop, error: Error, logger: Logger) -> Task<Response> {
646-
let task = self.init(eventLoop: eventLoop, logger: logger)
639+
static func failedTask(
640+
eventLoop: EventLoop,
641+
error: Error,
642+
logger: Logger,
643+
makeOrGetFileIOThreadPool: @escaping () -> NIOThreadPool
644+
) -> Task<Response> {
645+
let task = self.init(eventLoop: eventLoop, logger: logger, makeOrGetFileIOThreadPool: makeOrGetFileIOThreadPool)
647646
task.promise.fail(error)
648647
return task
649648
}

Tests/AsyncHTTPClientTests/RequestBagTests.swift

+14
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,20 @@ final class RequestBagTests: XCTestCase {
771771
}
772772
}
773773

774+
import NIOPosix
775+
776+
extension HTTPClient.Task {
777+
convenience init(
778+
eventLoop: EventLoop,
779+
logger: Logger
780+
) {
781+
lazy var threadPool = NIOThreadPool(numberOfThreads: 1)
782+
self.init(eventLoop: eventLoop, logger: logger) {
783+
threadPool
784+
}
785+
}
786+
}
787+
774788
class UploadCountingDelegate: HTTPClientResponseDelegate {
775789
typealias Response = Void
776790

0 commit comments

Comments
 (0)