From 0b4b92dd362578d3956980ca3847d9d7b617ecd1 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Thu, 5 Sep 2024 13:33:16 +0200 Subject: [PATCH 1/5] Opt into Swift 6 language mode for most targets --- Package.swift | 3 +-- Sources/AWSLambdaRuntime/Lambda+Codable.swift | 4 ++-- Sources/AWSLambdaRuntimeCore/Lambda.swift | 2 +- .../MockLambdaServer.swift | 13 +++++++++++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Package.swift b/Package.swift index 8bbc061f..2550ad34 100644 --- a/Package.swift +++ b/Package.swift @@ -33,8 +33,7 @@ let package = Package( .byName(name: "AWSLambdaRuntimeCore"), .product(name: "NIOCore", package: "swift-nio"), .product(name: "NIOFoundationCompat", package: "swift-nio"), - ], - swiftSettings: [.swiftLanguageMode(.v5)] + ] ), .target( name: "AWSLambdaRuntimeCore", diff --git a/Sources/AWSLambdaRuntime/Lambda+Codable.swift b/Sources/AWSLambdaRuntime/Lambda+Codable.swift index 8dc848d1..fc682920 100644 --- a/Sources/AWSLambdaRuntime/Lambda+Codable.swift +++ b/Sources/AWSLambdaRuntime/Lambda+Codable.swift @@ -71,7 +71,7 @@ extension LambdaRuntime { /// - Parameter encoder: The encoder object that will be used to encode the generic `Output` into a `ByteBuffer`. `JSONEncoder()` used as default. /// - Parameter decoder: The decoder object that will be used to decode the incoming `ByteBuffer` event into the generic `Event` type. `JSONDecoder()` used as default. public convenience init( - body: @escaping (Event, LambdaContext) async throws -> Output, + body: sending @escaping (Event, LambdaContext) async throws -> Output, encoder: JSONEncoder = JSONEncoder(), decoder: JSONDecoder = JSONDecoder() ) @@ -97,7 +97,7 @@ extension LambdaRuntime { /// - Parameter body: The handler in the form of a closure. /// - Parameter decoder: The decoder object that will be used to decode the incoming `ByteBuffer` event into the generic `Event` type. `JSONDecoder()` used as default. public convenience init( - body: @escaping (Event, LambdaContext) async throws -> Void, + body: sending @escaping (Event, LambdaContext) async throws -> Void, decoder: JSONDecoder = JSONDecoder() ) where diff --git a/Sources/AWSLambdaRuntimeCore/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift index 87026693..3ba90e9c 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda.swift @@ -60,7 +60,7 @@ public enum Lambda { } /// The default EventLoop the Lambda is scheduled on. - public static var defaultEventLoop: any EventLoop = NIOSingletons.posixEventLoopGroup.next() + public static let defaultEventLoop: any EventLoop = NIOSingletons.posixEventLoopGroup.next() } // MARK: - Public API diff --git a/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift b/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift index 1d56da69..63c9ffeb 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift @@ -27,7 +27,7 @@ func withMockServer( _ body: (_ port: Int) async throws -> Result ) async throws -> Result { let eventLoopGroup = NIOSingletons.posixEventLoopGroup - let server = MockLambdaServer(behavior: behaviour, port: port, keepAlive: keepAlive) + let server = MockLambdaServer(behavior: behaviour, port: port, keepAlive: keepAlive, eventLoopGroup: eventLoopGroup) let port = try await server.start().get() let result: Swift.Result @@ -52,7 +52,13 @@ final class MockLambdaServer { private var channel: Channel? private var shutdown = false - init(behavior: LambdaServerBehavior, host: String = "127.0.0.1", port: Int = 7000, keepAlive: Bool = true) { + init( + behavior: LambdaServerBehavior, + host: String = "127.0.0.1", + port: Int = 7000, + keepAlive: Bool = true, + eventLoopGroup: MultiThreadedEventLoopGroup + ) { self.group = NIOSingletons.posixEventLoopGroup self.behavior = behavior self.host = host @@ -233,11 +239,14 @@ final class HTTPHandler: ChannelInboundHandler { } } + let loopBoundContext = NIOLoopBound(context, eventLoop: context.eventLoop) + context.writeAndFlush(wrapOutboundOut(.end(nil))).whenComplete { result in if case .failure(let error) = result { self.logger.error("\(self) write error \(error)") } if !self.keepAlive { + let context = loopBoundContext.value context.close().whenFailure { error in self.logger.error("\(self) close error \(error)") } From d2ca302775dd65f822b865488e04fdfbd4ada9a7 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Thu, 5 Sep 2024 14:55:22 +0200 Subject: [PATCH 2/5] more work --- .../MockLambdaServer.swift | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift b/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift index 63c9ffeb..e06bec9d 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift @@ -28,7 +28,7 @@ func withMockServer( ) async throws -> Result { let eventLoopGroup = NIOSingletons.posixEventLoopGroup let server = MockLambdaServer(behavior: behaviour, port: port, keepAlive: keepAlive, eventLoopGroup: eventLoopGroup) - let port = try await server.start().get() + let port = try await server.start() let result: Swift.Result do { @@ -41,19 +41,19 @@ func withMockServer( return try result.get() } -final class MockLambdaServer { +final class MockLambdaServer { private let logger = Logger(label: "MockLambdaServer") - private let behavior: LambdaServerBehavior + private let behavior: Behavior private let host: String private let port: Int private let keepAlive: Bool private let group: EventLoopGroup private var channel: Channel? - private var shutdown = false + private var shutdown = false, n,m init( - behavior: LambdaServerBehavior, + behavior: Behavior, host: String = "127.0.0.1", port: Int = 7000, keepAlive: Bool = true, @@ -70,28 +70,33 @@ final class MockLambdaServer { assert(shutdown) } - func start() -> EventLoopFuture { - let bootstrap = ServerBootstrap(group: group) + func start() async throws -> Int { + let logger = self.logger + let keepAlive = self.keepAlive + let behavior = self.behavior + + let channel = try await ServerBootstrap(group: group) .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) .childChannelInitializer { channel in do { try channel.pipeline.syncOperations.configureHTTPServerPipeline(withErrorHandling: true) try channel.pipeline.syncOperations.addHandler( - HTTPHandler(logger: self.logger, keepAlive: self.keepAlive, behavior: self.behavior) + HTTPHandler(logger: logger, keepAlive: keepAlive, behavior: behavior) ) return channel.eventLoop.makeSucceededVoidFuture() } catch { return channel.eventLoop.makeFailedFuture(error) } } - return bootstrap.bind(host: self.host, port: self.port).flatMap { channel in - self.channel = channel - guard let localAddress = channel.localAddress else { - return channel.eventLoop.makeFailedFuture(ServerError.cantBind) - } - self.logger.info("\(self) started and listening on \(localAddress)") - return channel.eventLoop.makeSucceededFuture(localAddress.port!) + .bind(host: self.host, port: self.port) + .get() + + self.channel = channel + guard let localAddress = channel.localAddress else { + throw ServerError.cantBind } + self.logger.info("\(self) started and listening on \(localAddress)") + return localAddress.port! } func stop() -> EventLoopFuture { @@ -255,7 +260,7 @@ final class HTTPHandler: ChannelInboundHandler { } } -protocol LambdaServerBehavior { +protocol LambdaServerBehavior: Sendable { func getInvocation() -> GetInvocationResult func processResponse(requestId: String, response: String?) -> Result func processError(requestId: String, error: ErrorResponse) -> Result From 3b4c37510e3f9353b10250f980775bf9f2ed0257 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Fri, 6 Sep 2024 11:30:23 +0200 Subject: [PATCH 3/5] Test target Swift 6 --- .../MockLambdaServer.swift | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift b/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift index e06bec9d..cc3c4ac2 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift @@ -37,7 +37,7 @@ func withMockServer( result = .failure(error) } - try? await server.stop().get() + try? await server.stop() return try result.get() } @@ -50,7 +50,7 @@ final class MockLambdaServer { private let group: EventLoopGroup private var channel: Channel? - private var shutdown = false, n,m + private var shutdown = false init( behavior: Behavior, @@ -70,7 +70,7 @@ final class MockLambdaServer { assert(shutdown) } - func start() async throws -> Int { + fileprivate func start() async throws -> Int { let logger = self.logger let keepAlive = self.keepAlive let behavior = self.behavior @@ -99,15 +99,12 @@ final class MockLambdaServer { return localAddress.port! } - func stop() -> EventLoopFuture { + fileprivate func stop() async throws { self.logger.info("stopping \(self)") - guard let channel = self.channel else { - return self.group.next().makeFailedFuture(ServerError.notReady) - } - return channel.close().always { _ in - self.shutdown = true - self.logger.info("\(self) stopped") - } + let channel = self.channel! + try? await channel.close().get() + self.shutdown = true + self.logger.info("\(self) stopped") } } @@ -232,28 +229,30 @@ final class HTTPHandler: ChannelInboundHandler { } let head = HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: status, headers: headers) + let logger = self.logger context.write(wrapOutboundOut(.head(head))).whenFailure { error in - self.logger.error("\(self) write error \(error)") + logger.error("write error \(error)") } if let b = body { var buffer = context.channel.allocator.buffer(capacity: b.utf8.count) buffer.writeString(b) context.write(wrapOutboundOut(.body(.byteBuffer(buffer)))).whenFailure { error in - self.logger.error("\(self) write error \(error)") + logger.error("write error \(error)") } } let loopBoundContext = NIOLoopBound(context, eventLoop: context.eventLoop) + let keepAlive = self.keepAlive context.writeAndFlush(wrapOutboundOut(.end(nil))).whenComplete { result in if case .failure(let error) = result { - self.logger.error("\(self) write error \(error)") + logger.error("write error \(error)") } - if !self.keepAlive { + if !keepAlive { let context = loopBoundContext.value context.close().whenFailure { error in - self.logger.error("\(self) close error \(error)") + logger.error("close error \(error)") } } } From a909dd844366a573d1795ab3bf516b85c209bfd5 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Fri, 6 Sep 2024 11:36:05 +0200 Subject: [PATCH 4/5] More fixes for Swift 6 --- Sources/AWSLambdaRuntimeCore/LambdaHandlers.swift | 4 ++-- Sources/AWSLambdaRuntimeCore/LambdaRequestID.swift | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/AWSLambdaRuntimeCore/LambdaHandlers.swift b/Sources/AWSLambdaRuntimeCore/LambdaHandlers.swift index 9b6f8300..b76b453d 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaHandlers.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaHandlers.swift @@ -194,7 +194,7 @@ extension LambdaRuntime { >( encoder: Encoder, decoder: Decoder, - body: @escaping (Event, LambdaContext) async throws -> Output + body: sending @escaping (Event, LambdaContext) async throws -> Output ) where Handler == LambdaCodableAdapter< @@ -220,7 +220,7 @@ extension LambdaRuntime { /// - body: The handler in the form of a closure. public convenience init( decoder: Decoder, - body: @escaping (Event, LambdaContext) async throws -> Void + body: sending @escaping (Event, LambdaContext) async throws -> Void ) where Handler == LambdaCodableAdapter< diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRequestID.swift b/Sources/AWSLambdaRuntimeCore/LambdaRequestID.swift index 22a4275e..df576947 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRequestID.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRequestID.swift @@ -88,15 +88,16 @@ struct LambdaRequestID { } /// thread safe secure random number generator. - private static var generator = SystemRandomNumberGenerator() private static func generateRandom() -> Self { + var generator = SystemRandomNumberGenerator() + var _uuid: uuid_t = LambdaRequestID.null // https://tools.ietf.org/html/rfc4122#page-14 // o Set all the other bits to randomly (or pseudo-randomly) chosen // values. withUnsafeMutableBytes(of: &_uuid) { ptr in - ptr.storeBytes(of: Self.generator.next(), toByteOffset: 0, as: UInt64.self) - ptr.storeBytes(of: Self.generator.next(), toByteOffset: 8, as: UInt64.self) + ptr.storeBytes(of: generator.next(), toByteOffset: 0, as: UInt64.self) + ptr.storeBytes(of: generator.next(), toByteOffset: 8, as: UInt64.self) } // o Set the four most significant bits (bits 12 through 15) of the From a81bec7a9312d180bffa99026e77136bf1d902ed Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Tue, 8 Oct 2024 18:31:21 +0200 Subject: [PATCH 5/5] fix parameter order to allow trailing closure syntax --- Sources/AWSLambdaRuntime/Lambda+Codable.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Sources/AWSLambdaRuntime/Lambda+Codable.swift b/Sources/AWSLambdaRuntime/Lambda+Codable.swift index fc682920..2ab63855 100644 --- a/Sources/AWSLambdaRuntime/Lambda+Codable.swift +++ b/Sources/AWSLambdaRuntime/Lambda+Codable.swift @@ -67,13 +67,14 @@ extension LambdaCodableAdapter { extension LambdaRuntime { /// Initialize an instance with a `LambdaHandler` defined in the form of a closure **with a non-`Void` return type**. - /// - Parameter body: The handler in the form of a closure. - /// - Parameter encoder: The encoder object that will be used to encode the generic `Output` into a `ByteBuffer`. `JSONEncoder()` used as default. - /// - Parameter decoder: The decoder object that will be used to decode the incoming `ByteBuffer` event into the generic `Event` type. `JSONDecoder()` used as default. + /// - Parameters: + /// - decoder: The decoder object that will be used to decode the incoming `ByteBuffer` event into the generic `Event` type. `JSONDecoder()` used as default. + /// - encoder: The encoder object that will be used to encode the generic `Output` into a `ByteBuffer`. `JSONEncoder()` used as default. + /// - body: The handler in the form of a closure. public convenience init( - body: sending @escaping (Event, LambdaContext) async throws -> Output, + decoder: JSONDecoder = JSONDecoder(), encoder: JSONEncoder = JSONEncoder(), - decoder: JSONDecoder = JSONDecoder() + body: sending @escaping (Event, LambdaContext) async throws -> Output ) where Handler == LambdaCodableAdapter< @@ -97,8 +98,8 @@ extension LambdaRuntime { /// - Parameter body: The handler in the form of a closure. /// - Parameter decoder: The decoder object that will be used to decode the incoming `ByteBuffer` event into the generic `Event` type. `JSONDecoder()` used as default. public convenience init( - body: sending @escaping (Event, LambdaContext) async throws -> Void, - decoder: JSONDecoder = JSONDecoder() + decoder: JSONDecoder = JSONDecoder(), + body: sending @escaping (Event, LambdaContext) async throws -> Void ) where Handler == LambdaCodableAdapter<