diff --git a/Package.swift b/Package.swift index 1f2f0046f..7e80f853e 100644 --- a/Package.swift +++ b/Package.swift @@ -21,11 +21,11 @@ let package = Package( .library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", from: "2.50.0"), + .package(url: "https://github.com/apple/swift-nio.git", from: "2.58.0"), .package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.22.0"), .package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.19.0"), .package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.13.0"), - .package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.11.4"), + .package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.19.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.4.4"), .package(url: "https://github.com/apple/swift-atomics.git", from: "1.0.2"), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), diff --git a/README.md b/README.md index e969c54ff..806cc2960 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,10 @@ and `AsyncHTTPClient` dependency to your target: The code snippet below illustrates how to make a simple GET request to a remote server. -Please note that the example will spawn a new `EventLoopGroup` which will _create fresh threads_ which is a very costly operation. In a real-world application that uses [SwiftNIO](https://github.com/apple/swift-nio) for other parts of your application (for example a web server), please prefer `eventLoopGroupProvider: .shared(myExistingEventLoopGroup)` to share the `EventLoopGroup` used by AsyncHTTPClient with other parts of your application. - -If your application does not use SwiftNIO yet, it is acceptable to use `eventLoopGroupProvider: .createNew` but please make sure to share the returned `HTTPClient` instance throughout your whole application. Do not create a large number of `HTTPClient` instances with `eventLoopGroupProvider: .createNew`, this is very wasteful and might exhaust the resources of your program. - ```swift import AsyncHTTPClient -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) /// MARK: - Using Swift Concurrency let request = HTTPClientRequest(url: "https://apple.com/") @@ -78,7 +74,7 @@ The default HTTP Method is `GET`. In case you need to have more control over the ```swift import AsyncHTTPClient -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) do { var request = HTTPClientRequest(url: "https://apple.com/") request.method = .POST @@ -103,9 +99,10 @@ try await httpClient.shutdown() ```swift import AsyncHTTPClient -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) defer { - try? httpClient.syncShutdown() + // Shutdown is guaranteed to work if it's done precisely once (which is the case here). + try! httpClient.syncShutdown() } var request = try HTTPClient.Request(url: "https://apple.com/", method: .POST) @@ -129,7 +126,7 @@ httpClient.execute(request: request).whenComplete { result in ### Redirects following Enable follow-redirects behavior using the client configuration: ```swift -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton, configuration: HTTPClient.Configuration(followRedirects: true)) ``` @@ -137,7 +134,7 @@ let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, Timeouts (connect and read) can also be set using the client configuration: ```swift let timeout = HTTPClient.Configuration.Timeout(connect: .seconds(1), read: .seconds(1)) -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton, configuration: HTTPClient.Configuration(timeout: timeout)) ``` or on a per-request basis: @@ -151,7 +148,7 @@ The following example demonstrates how to count the number of bytes in a streami #### Using Swift Concurrency ```swift -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) do { let request = HTTPClientRequest(url: "https://apple.com/") let response = try await httpClient.execute(request, timeout: .seconds(30)) @@ -251,7 +248,7 @@ asynchronously, while reporting the download progress at the same time, like in example: ```swift -let client = HTTPClient(eventLoopGroupProvider: .createNew) +let client = HTTPClient(eventLoopGroupProvider: .singleton) let request = try HTTPClient.Request( url: "https://swift.org/builds/development/ubuntu1804/latest-build.yml" ) @@ -275,7 +272,7 @@ client.execute(request: request, delegate: delegate).futureResult ### Unix Domain Socket Paths Connecting to servers bound to socket paths is easy: ```swift -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) httpClient.execute( .GET, socketPath: "/tmp/myServer.socket", @@ -285,7 +282,7 @@ httpClient.execute( Connecting over TLS to a unix domain socket path is possible as well: ```swift -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) httpClient.execute( .POST, secureSocketPath: "/tmp/myServer.socket", @@ -312,7 +309,7 @@ The exclusive use of HTTP/1 is possible by setting `httpVersion` to `.http1Only` var configuration = HTTPClient.Configuration() configuration.httpVersion = .http1Only let client = HTTPClient( - eventLoopGroupProvider: .createNew, + eventLoopGroupProvider: .singleton, configuration: configuration ) ``` diff --git a/Sources/AsyncHTTPClient/Docs.docc/index.md b/Sources/AsyncHTTPClient/Docs.docc/index.md index 66a9d1135..82e859b03 100644 --- a/Sources/AsyncHTTPClient/Docs.docc/index.md +++ b/Sources/AsyncHTTPClient/Docs.docc/index.md @@ -31,14 +31,14 @@ and `AsyncHTTPClient` dependency to your target: The code snippet below illustrates how to make a simple GET request to a remote server. -Please note that the example will spawn a new `EventLoopGroup` which will _create fresh threads_ which is a very costly operation. In a real-world application that uses [SwiftNIO](https://github.com/apple/swift-nio) for other parts of your application (for example a web server), please prefer `eventLoopGroupProvider: .shared(myExistingEventLoopGroup)` to share the `EventLoopGroup` used by AsyncHTTPClient with other parts of your application. - -If your application does not use SwiftNIO yet, it is acceptable to use `eventLoopGroupProvider: .createNew` but please make sure to share the returned `HTTPClient` instance throughout your whole application. Do not create a large number of `HTTPClient` instances with `eventLoopGroupProvider: .createNew`, this is very wasteful and might exhaust the resources of your program. - ```swift import AsyncHTTPClient -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) +defer { + // Shutdown is guaranteed to work if it's done precisely once (which is the case here). + try! httpClient.syncShutdown() +} /// MARK: - Using Swift Concurrency let request = HTTPClientRequest(url: "https://apple.com/") @@ -82,7 +82,12 @@ The default HTTP Method is `GET`. In case you need to have more control over the ```swift import AsyncHTTPClient -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) +defer { + // Shutdown is guaranteed to work if it's done precisely once (which is the case here). + try! httpClient.syncShutdown() +} + do { var request = HTTPClientRequest(url: "https://apple.com/") request.method = .POST @@ -107,9 +112,10 @@ try await httpClient.shutdown() ```swift import AsyncHTTPClient -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) defer { - try? httpClient.syncShutdown() + // Shutdown is guaranteed to work if it's done precisely once (which is the case here). + try! httpClient.syncShutdown() } var request = try HTTPClient.Request(url: "https://apple.com/", method: .POST) @@ -133,7 +139,7 @@ httpClient.execute(request: request).whenComplete { result in #### Redirects following Enable follow-redirects behavior using the client configuration: ```swift -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton, configuration: HTTPClient.Configuration(followRedirects: true)) ``` @@ -141,7 +147,7 @@ let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, Timeouts (connect and read) can also be set using the client configuration: ```swift let timeout = HTTPClient.Configuration.Timeout(connect: .seconds(1), read: .seconds(1)) -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew, +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton, configuration: HTTPClient.Configuration(timeout: timeout)) ``` or on a per-request basis: @@ -155,7 +161,12 @@ The following example demonstrates how to count the number of bytes in a streami ##### Using Swift Concurrency ```swift -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) +defer { + // Shutdown is guaranteed to work if it's done precisely once (which is the case here). + try! httpClient.syncShutdown() +} + do { let request = HTTPClientRequest(url: "https://apple.com/") let response = try await httpClient.execute(request, timeout: .seconds(30)) @@ -255,7 +266,12 @@ asynchronously, while reporting the download progress at the same time, like in example: ```swift -let client = HTTPClient(eventLoopGroupProvider: .createNew) +let client = HTTPClient(eventLoopGroupProvider: .singleton) +defer { + // Shutdown is guaranteed to work if it's done precisely once (which is the case here). + try! httpClient.syncShutdown() +} + let request = try HTTPClient.Request( url: "https://swift.org/builds/development/ubuntu1804/latest-build.yml" ) @@ -279,7 +295,12 @@ client.execute(request: request, delegate: delegate).futureResult #### Unix Domain Socket Paths Connecting to servers bound to socket paths is easy: ```swift -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) +defer { + // Shutdown is guaranteed to work if it's done precisely once (which is the case here). + try! httpClient.syncShutdown() +} + httpClient.execute( .GET, socketPath: "/tmp/myServer.socket", @@ -289,7 +310,12 @@ httpClient.execute( Connecting over TLS to a unix domain socket path is possible as well: ```swift -let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) +let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) +defer { + // Shutdown is guaranteed to work if it's done precisely once (which is the case here). + try! httpClient.syncShutdown() +} + httpClient.execute( .POST, secureSocketPath: "/tmp/myServer.socket", @@ -316,7 +342,7 @@ The exclusive use of HTTP/1 is possible by setting ``HTTPClient/Configuration/ht var configuration = HTTPClient.Configuration() configuration.httpVersion = .http1Only let client = HTTPClient( - eventLoopGroupProvider: .createNew, + eventLoopGroupProvider: .singleton, configuration: configuration ) ``` diff --git a/Sources/AsyncHTTPClient/HTTPClient.swift b/Sources/AsyncHTTPClient/HTTPClient.swift index de6b57087..53da87e75 100644 --- a/Sources/AsyncHTTPClient/HTTPClient.swift +++ b/Sources/AsyncHTTPClient/HTTPClient.swift @@ -44,7 +44,7 @@ let globalRequestID = ManagedAtomic(0) /// Example: /// /// ```swift -/// let client = HTTPClient(eventLoopGroupProvider: .createNew) +/// let client = HTTPClient(eventLoopGroupProvider: .singleton) /// client.get(url: "https://swift.org", deadline: .now() + .seconds(1)).whenComplete { result in /// switch result { /// case .failure(let error): @@ -69,7 +69,6 @@ public class HTTPClient { /// /// All HTTP transactions will occur on loops owned by this group. public let eventLoopGroup: EventLoopGroup - let eventLoopGroupProvider: EventLoopGroupProvider let configuration: Configuration let poolManager: HTTPConnectionPool.Manager @@ -94,29 +93,50 @@ public class HTTPClient { backgroundActivityLogger: HTTPClient.loggingDisabled) } + /// Create an ``HTTPClient`` with specified `EventLoopGroup` and configuration. + /// + /// - parameters: + /// - eventLoopGroupProvider: Specify how `EventLoopGroup` will be created. + /// - configuration: Client configuration. + public convenience init(eventLoopGroup: EventLoopGroup = HTTPClient.defaultEventLoopGroup, + configuration: Configuration = Configuration()) { + self.init(eventLoopGroupProvider: .shared(eventLoopGroup), + configuration: configuration, + backgroundActivityLogger: HTTPClient.loggingDisabled) + } + /// Create an ``HTTPClient`` with specified `EventLoopGroup` provider and configuration. /// /// - parameters: /// - eventLoopGroupProvider: Specify how `EventLoopGroup` will be created. /// - configuration: Client configuration. - public required init(eventLoopGroupProvider: EventLoopGroupProvider, - configuration: Configuration = Configuration(), - backgroundActivityLogger: Logger) { - self.eventLoopGroupProvider = eventLoopGroupProvider - switch self.eventLoopGroupProvider { + public convenience init(eventLoopGroupProvider: EventLoopGroupProvider, + configuration: Configuration = Configuration(), + backgroundActivityLogger: Logger) { + let eventLoopGroup: any EventLoopGroup + + switch eventLoopGroupProvider { case .shared(let group): - self.eventLoopGroup = group - case .createNew: - #if canImport(Network) - if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { - self.eventLoopGroup = NIOTSEventLoopGroup() - } else { - self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - } - #else - self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - #endif + eventLoopGroup = group + default: // handle `.createNew` without a deprecation warning + eventLoopGroup = HTTPClient.defaultEventLoopGroup } + + self.init(eventLoopGroup: eventLoopGroup, + configuration: configuration, + backgroundActivityLogger: backgroundActivityLogger) + } + + /// Create an ``HTTPClient`` with specified `EventLoopGroup` and configuration. + /// + /// - parameters: + /// - eventLoopGroup: The `EventLoopGroup` that the ``HTTPClient`` will use. + /// - configuration: Client configuration. + /// - backgroundActivityLogger: The `Logger` that will be used to log background any activity that's not associated with a request. + public required init(eventLoopGroup: any EventLoopGroup, + configuration: Configuration = Configuration(), + backgroundActivityLogger: Logger) { + self.eventLoopGroup = eventLoopGroup self.configuration = configuration self.poolManager = HTTPConnectionPool.Manager( eventLoopGroup: self.eventLoopGroup, @@ -214,55 +234,16 @@ public class HTTPClient { } /// Shuts down the ``HTTPClient`` and releases its resources. - /// - /// - note: You cannot use this method if you sharted the ``HTTPClient`` with - /// `init(eventLoopGroupProvider: .createNew)` because that will shut down the `EventLoopGroup` the - /// returned future would run in. public func shutdown() -> EventLoopFuture { - switch self.eventLoopGroupProvider { - case .shared(let group): - let promise = group.any().makePromise(of: Void.self) - self.shutdown(queue: .global()) { error in - if let error = error { - promise.fail(error) - } else { - promise.succeed(()) - } - } - return promise.futureResult - case .createNew: - preconditionFailure("Cannot use the shutdown() method which returns a future when owning the EventLoopGroup. Please use the one of the other shutdown methods.") - } - } - - private func shutdownEventLoop(queue: DispatchQueue, _ callback: @escaping ShutdownCallback) { - self.stateLock.withLock { - switch self.eventLoopGroupProvider { - case .shared: - self.state = .shutDown - queue.async { - callback(nil) - } - case .createNew: - switch self.state { - case .shuttingDown: - self.state = .shutDown - self.eventLoopGroup.shutdownGracefully(queue: queue, callback) - case .shutDown, .upAndRunning: - assertionFailure("The only valid state at this point is \(String(describing: State.shuttingDown))") - } - } - } - } - - private func shutdownFileIOThreadPool(queue: DispatchQueue, _ callback: @escaping ShutdownCallback) { - self.fileIOThreadPoolLock.withLock { - guard let fileIOThreadPool = fileIOThreadPool else { - callback(nil) - return + let promise = self.eventLoopGroup.any().makePromise(of: Void.self) + self.shutdown(queue: .global()) { error in + if let error = error { + promise.fail(error) + } else { + promise.succeed(()) } - fileIOThreadPool.shutdownGracefully(queue: queue, callback) } + return promise.futureResult } private func shutdown(requiresCleanClose: Bool, queue: DispatchQueue, _ callback: @escaping ShutdownCallback) { @@ -293,11 +274,11 @@ public class HTTPClient { let error: Error? = (requiresClean && unclean) ? HTTPClientError.uncleanShutdown : nil return (callback, error) } - self.shutdownFileIOThreadPool(queue: queue) { ioThreadPoolError in - self.shutdownEventLoop(queue: queue) { error in - let reportedError = error ?? ioThreadPoolError ?? uncleanError - callback(reportedError) - } + self.stateLock.withLock { + self.state = .shutDown + } + queue.async { + callback(uncleanError) } } } @@ -305,11 +286,8 @@ public class HTTPClient { private func makeOrGetFileIOThreadPool() -> NIOThreadPool { self.fileIOThreadPoolLock.withLock { - guard let fileIOThreadPool = fileIOThreadPool else { - let fileIOThreadPool = NIOThreadPool(numberOfThreads: System.coreCount) - fileIOThreadPool.start() - self.fileIOThreadPool = fileIOThreadPool - return fileIOThreadPool + guard let fileIOThreadPool = self.fileIOThreadPool else { + return NIOThreadPool.singleton } return fileIOThreadPool } @@ -853,7 +831,12 @@ public class HTTPClient { public enum EventLoopGroupProvider { /// `EventLoopGroup` will be provided by the user. Owner of this group is responsible for its lifecycle. case shared(EventLoopGroup) - /// `EventLoopGroup` will be created by the client. When ``HTTPClient/syncShutdown()`` is called, the created `EventLoopGroup` will be shut down as well. + /// The original intention of this was that ``HTTPClient`` would create and own its own `EventLoopGroup` to + /// facilitate use in programs that are not already using SwiftNIO. + /// Since https://github.com/apple/swift-nio/pull/2471 however, SwiftNIO does provide a global, shared singleton + /// `EventLoopGroup`s that we can use. ``HTTPClient`` is no longer able to create & own its own + /// `EventLoopGroup` which solves a whole host of issues around shutdown. + @available(*, deprecated, renamed: "singleton", message: "Please use the singleton EventLoopGroup explicitly") case createNew } @@ -914,6 +897,30 @@ public class HTTPClient { } } +extension HTTPClient.EventLoopGroupProvider { + /// Shares ``HTTPClient/defaultEventLoopGroup`` which is a singleton `EventLoopGroup` suitable for the platform. + public static var singleton: Self { + return .shared(HTTPClient.defaultEventLoopGroup) + } +} + +extension HTTPClient { + /// Returns the default `EventLoopGroup` singleton, automatically selecting the best for the platform. + /// + /// This will select the concrete `EventLoopGroup` depending which platform this is running on. + public static var defaultEventLoopGroup: EventLoopGroup { + #if canImport(Network) + if #available(OSX 10.14, iOS 12.0, tvOS 12.0, watchOS 6.0, *) { + return NIOTSEventLoopGroup.singleton + } else { + return MultiThreadedEventLoopGroup.singleton + } + #else + return MultiThreadedEventLoopGroup.singleton + #endif + } +} + #if swift(>=5.7) extension HTTPClient.Configuration: Sendable {} #endif diff --git a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift index 994e331fb..d26f5ae24 100644 --- a/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift +++ b/Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift @@ -20,7 +20,7 @@ import NIOSSL import XCTest private func makeDefaultHTTPClient( - eventLoopGroupProvider: HTTPClient.EventLoopGroupProvider = .createNew + eventLoopGroupProvider: HTTPClient.EventLoopGroupProvider = .singleton ) -> HTTPClient { var config = HTTPClient.Configuration() config.tlsConfiguration = .clientDefault @@ -504,7 +504,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { let config = HTTPClient.Configuration() .enableFastFailureModeForTesting() - let localClient = HTTPClient(eventLoopGroupProvider: .createNew, configuration: config) + let localClient = HTTPClient(eventLoopGroupProvider: .singleton, configuration: config) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } let request = HTTPClientRequest(url: "https://localhost:\(port)") await XCTAssertThrowsError(try await localClient.execute(request, deadline: .now() + .seconds(2))) { error in @@ -570,7 +570,7 @@ final class AsyncAwaitEndToEndTests: XCTestCase { // this is the actual configuration under test config.dnsOverride = ["example.com": "localhost"] - let localClient = HTTPClient(eventLoopGroupProvider: .createNew, configuration: config) + let localClient = HTTPClient(eventLoopGroupProvider: .singleton, configuration: config) defer { XCTAssertNoThrow(try localClient.syncShutdown()) } let request = HTTPClientRequest(url: "https://example.com:\(bin.port)/echohostheader") let response = await XCTAssertNoThrowWithResult(try await localClient.execute(request, deadline: .now() + .seconds(2))) diff --git a/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift b/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift index 7d0aaad0c..71eb0c44b 100644 --- a/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTP2ClientTests.swift @@ -25,7 +25,7 @@ import XCTest class HTTP2ClientTests: XCTestCase { func makeDefaultHTTPClient( - eventLoopGroupProvider: HTTPClient.EventLoopGroupProvider = .createNew + eventLoopGroupProvider: HTTPClient.EventLoopGroupProvider = .singleton ) -> HTTPClient { var config = HTTPClient.Configuration() config.tlsConfiguration = .clientDefault @@ -40,7 +40,7 @@ class HTTP2ClientTests: XCTestCase { func makeClientWithActiveHTTP2Connection( to bin: HTTPBin, - eventLoopGroupProvider: HTTPClient.EventLoopGroupProvider = .createNew + eventLoopGroupProvider: HTTPClient.EventLoopGroupProvider = .singleton ) -> HTTPClient { let client = self.makeDefaultHTTPClient(eventLoopGroupProvider: eventLoopGroupProvider) var response: HTTPClient.Response? @@ -301,7 +301,7 @@ class HTTP2ClientTests: XCTestCase { config.httpVersion = .automatic config.timeout.read = .milliseconds(100) let client = HTTPClient( - eventLoopGroupProvider: .createNew, + eventLoopGroupProvider: .singleton, configuration: config, backgroundActivityLogger: Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) ) @@ -322,7 +322,8 @@ class HTTP2ClientTests: XCTestCase { config.tlsConfiguration = tlsConfig config.httpVersion = .automatic let client = HTTPClient( - eventLoopGroupProvider: .createNew, + // TODO: Test fails if the provided ELG is a multi-threaded NIOTSEventLoopGroup (probably racy) + eventLoopGroupProvider: .shared(bin.group), configuration: config, backgroundActivityLogger: Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:)) ) diff --git a/Tests/AsyncHTTPClientTests/HTTPClientInformationalResponsesTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientInformationalResponsesTests.swift index f57d5fd10..3ca6e1054 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientInformationalResponsesTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientInformationalResponsesTests.swift @@ -37,7 +37,7 @@ final class HTTPClientReproTests: XCTestCase { } } - let client = HTTPClient(eventLoopGroupProvider: .createNew) + let client = HTTPClient(eventLoopGroupProvider: .singleton) defer { XCTAssertNoThrow(try client.syncShutdown()) } let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in @@ -91,7 +91,7 @@ final class HTTPClientReproTests: XCTestCase { } } - let client = HTTPClient(eventLoopGroupProvider: .createNew) + let client = HTTPClient(eventLoopGroupProvider: .singleton) defer { XCTAssertNoThrow(try client.syncShutdown()) } let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in diff --git a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift index 3db4385cd..be03f6a6a 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift @@ -38,7 +38,7 @@ class HTTPClientNIOTSTests: XCTestCase { } func testCorrectEventLoopGroup() { - let httpClient = HTTPClient(eventLoopGroupProvider: .createNew) + let httpClient = HTTPClient(eventLoopGroupProvider: .singleton) defer { XCTAssertNoThrow(try httpClient.syncShutdown()) } diff --git a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift index f04f84d0a..218abaf82 100644 --- a/Tests/AsyncHTTPClientTests/HTTPClientTests.swift +++ b/Tests/AsyncHTTPClientTests/HTTPClientTests.swift @@ -3507,4 +3507,22 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass { requests: 300 ) } + + func testClientWithDefaultSingletonELG() throws { + let client = HTTPClient() + defer { + XCTAssertNoThrow(try client.shutdown().wait()) + } + let response = try client.get(url: self.defaultHTTPBinURLPrefix + "get").wait() + XCTAssertEqual(.ok, response.status) + } + + func testClientWithELGInit() throws { + let client = HTTPClient(eventLoopGroup: MultiThreadedEventLoopGroup.singleton) + defer { + XCTAssertNoThrow(try client.shutdown().wait()) + } + let response = try client.get(url: self.defaultHTTPBinURLPrefix + "get").wait() + XCTAssertEqual(.ok, response.status) + } }