diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1d94b22..ce408a7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,6 +16,7 @@ jobs: with_android: true with_musl: true with_release_mode_testing: true + with_wasm: true submit-dependencies: permissions: diff --git a/Package.swift b/Package.swift index e2574d2..b4170cf 100644 --- a/Package.swift +++ b/Package.swift @@ -1,6 +1,10 @@ // swift-tools-version:6.0 import PackageDescription +let allPlatforms: [Platform] = [.macOS, .macCatalyst, .iOS, .tvOS, .watchOS, .visionOS, .driverKit, .linux, .windows, .android, .wasi, .openbsd] +let nonWASIPlatforms: [Platform] = allPlatforms.filter { $0 != .wasi } +let wasiPlatform: [Platform] = [.wasi] + let package = Package( name: "async-kit", platforms: [ @@ -13,7 +17,9 @@ let package = Package( .library(name: "AsyncKit", targets: ["AsyncKit"]), ], dependencies: [ + // WASM: upstream swift-nio (NIOCore is wasm-safe) + standalone NIOAsyncRuntime for wasi. .package(url: "https://github.com/apple/swift-nio.git", from: "2.95.0"), + .package(url: "https://github.com/PassiveLogic/nio-async-runtime.git", from: "1.0.0"), .package(url: "https://github.com/apple/swift-log.git", from: "1.10.1"), .package(url: "https://github.com/apple/swift-collections.git", from: "1.4.0"), .package(url: "https://github.com/apple/swift-algorithms.git", from: "1.2.1"), @@ -25,7 +31,8 @@ let package = Package( .product(name: "Logging", package: "swift-log"), .product(name: "NIOCore", package: "swift-nio"), .product(name: "NIOEmbedded", package: "swift-nio"), - .product(name: "NIOPosix", package: "swift-nio"), + .product(name: "NIOPosix", package: "swift-nio", condition: .when(platforms: nonWASIPlatforms)), + .product(name: "NIOAsyncRuntime", package: "nio-async-runtime", condition: .when(platforms: wasiPlatform)), .product(name: "Collections", package: "swift-collections"), .product(name: "Algorithms", package: "swift-algorithms"), ], @@ -35,6 +42,9 @@ let package = Package( name: "AsyncKitTests", dependencies: [ .target(name: "AsyncKit"), + .product(name: "NIOEmbedded", package: "swift-nio"), + .product(name: "NIOPosix", package: "swift-nio", condition: .when(platforms: nonWASIPlatforms)), + .product(name: "NIOAsyncRuntime", package: "nio-async-runtime", condition: .when(platforms: wasiPlatform)), ], swiftSettings: swiftSettings ), diff --git a/Sources/AsyncKit/ConnectionPool/EventLoopGroupConnectionPool.swift b/Sources/AsyncKit/ConnectionPool/EventLoopGroupConnectionPool.swift index 3c79b8f..9d649f5 100644 --- a/Sources/AsyncKit/ConnectionPool/EventLoopGroupConnectionPool.swift +++ b/Sources/AsyncKit/ConnectionPool/EventLoopGroupConnectionPool.swift @@ -1,4 +1,6 @@ +#if canImport(Dispatch) import Dispatch +#endif import NIOConcurrencyHelpers import NIOCore import struct Logging.Logger @@ -280,6 +282,7 @@ public final class EventLoopGroupConnectionPool where Source: Connection } } + #if canImport(Dispatch) /// Closes the connection pool. /// /// All available connections will be closed immediately. Any connections still in use will be @@ -312,6 +315,7 @@ public final class EventLoopGroupConnectionPool where Source: Connection } } } + #endif // canImport(Dispatch) /// Closes the connection pool. /// @@ -336,10 +340,17 @@ public final class EventLoopGroupConnectionPool where Source: Connection guard self.lock.withLock({ // Do not initiate shutdown multiple times. guard !self.didShutdown else { + #if canImport(Dispatch) DispatchQueue.global().async { self.logger.warning("Connection pool can not be shut down more than once.") callback(ConnectionPoolError.shutdown) } + #else + Task { + self.logger.warning("Connection pool can not be shut down more than once.") + callback(ConnectionPoolError.shutdown) + } + #endif return false } @@ -356,6 +367,7 @@ public final class EventLoopGroupConnectionPool where Source: Connection // all pools are closed, invoke the callback and provide it the first encountered error, if // any. By design, this loosely matches the general flow used by `MultiThreadedEventLoopGroup`'s // `shutdownGracefully(queue:_:)` implementation. + #if canImport(Dispatch) let shutdownQueue = DispatchQueue(label: "codes.vapor.async-kit.poolShutdownGracefullyQueue") let shutdownGroup = DispatchGroup() var outcome: Result = .success(()) @@ -380,6 +392,37 @@ public final class EventLoopGroupConnectionPool where Source: Connection callback(error) } } + #else + Task { + await withTaskGroup(of: Result.self) { group in + for pool in self.storage.values { + group.addTask { + let singleResult: Result = await withCheckedContinuation { continuation in + pool.close().whenComplete { result in + continuation.resume(returning: result) + } + } + + return singleResult + } + } + + var outcome: Result = .success(()) + for await singleResult in group { + outcome = outcome.flatMap { singleResult } + } + + switch outcome { + case .success: + self.logger.debug("Connection group pool finished shutdown.") + callback(nil) + case .failure(let error): + self.logger.error("Connection group pool got shutdown error (and then shut down anyway): \(error)") + callback(error) + } + } + } + #endif } deinit { diff --git a/Sources/AsyncKit/Exports.swift b/Sources/AsyncKit/Exports.swift index ebee4eb..35dc748 100644 --- a/Sources/AsyncKit/Exports.swift +++ b/Sources/AsyncKit/Exports.swift @@ -1,6 +1,12 @@ +#if canImport(NIOEmbedded) && !os(WASI) @_documentation(visibility: internal) @_exported import class NIOEmbedded.EmbeddedEventLoop +#endif @_documentation(visibility: internal) @_exported import protocol NIOCore.EventLoop @_documentation(visibility: internal) @_exported import protocol NIOCore.EventLoopGroup @_documentation(visibility: internal) @_exported import class NIOCore.EventLoopFuture @_documentation(visibility: internal) @_exported import struct NIOCore.EventLoopPromise +#if canImport(NIOPosix) && !os(WASI) @_documentation(visibility: internal) @_exported import class NIOPosix.MultiThreadedEventLoopGroup +#elseif os(WASI) +@_documentation(visibility: internal) @_exported import class NIOAsyncRuntime.MultiThreadedEventLoopGroup +#endif diff --git a/Tests/AsyncKitTests/AsyncKitTestsCommon.swift b/Tests/AsyncKitTests/AsyncKitTestsCommon.swift index 66c612e..78ade82 100644 --- a/Tests/AsyncKitTests/AsyncKitTestsCommon.swift +++ b/Tests/AsyncKitTests/AsyncKitTestsCommon.swift @@ -1,7 +1,11 @@ import XCTest import AsyncKit import NIOCore +#if os(WASI) +import NIOAsyncRuntime +#else import NIOPosix +#endif import Logging enum TestError: Error { diff --git a/Tests/AsyncKitTests/ConnectionPoolTests.swift b/Tests/AsyncKitTests/ConnectionPoolTests.swift index 6142d38..75b2b5e 100644 --- a/Tests/AsyncKitTests/ConnectionPoolTests.swift +++ b/Tests/AsyncKitTests/ConnectionPoolTests.swift @@ -4,6 +4,11 @@ import Logging import NIOConcurrencyHelpers import NIOCore import NIOEmbedded +#if os(WASI) +import class NIOAsyncRuntime.MultiThreadedEventLoopGroup +#else +import class NIOPosix.MultiThreadedEventLoopGroup +#endif import Testing @Suite