From a931e69bf4a4f8e26360da86db0bfbbd3b46d72d Mon Sep 17 00:00:00 2001 From: tom doron Date: Thu, 17 Mar 2022 19:53:32 -0700 Subject: [PATCH 1/5] adoption of sendable motivation: adopt to sendable requirments in swift 5.6 changes: * define sendable shims for protocols and structs that may be used in async context * adjust tests * add a test to make sure no warning are emittted --- .../AWSLambdaRuntimeCore/LambdaContext.swift | 30 +++++++----- .../AWSLambdaRuntimeCore/LambdaHandler.swift | 4 +- .../AWSLambdaRuntimeCore/LambdaRuntime.swift | 5 ++ .../LambdaRuntimeClient.swift | 5 ++ Sources/AWSLambdaRuntimeCore/Sendable.swift | 27 +++++++++++ Sources/AWSLambdaRuntimeCore/Terminator.swift | 10 +++- .../LambdaTest.swift | 48 ++++++++++++++++++- docker/Dockerfile | 2 +- 8 files changed, 113 insertions(+), 18 deletions(-) create mode 100644 Sources/AWSLambdaRuntimeCore/Sendable.swift diff --git a/Sources/AWSLambdaRuntimeCore/LambdaContext.swift b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift index d6fec657..6a0596f0 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaContext.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift @@ -12,9 +12,15 @@ // //===----------------------------------------------------------------------===// +#if compiler(>=5.6) +@preconcurrency import Dispatch +@preconcurrency import Logging +@preconcurrency import NIOCore +#else import Dispatch import Logging import NIOCore +#endif // MARK: - InitializationContext @@ -23,7 +29,7 @@ extension Lambda { /// The Lambda runtime generates and passes the `InitializationContext` to the Handlers /// ``ByteBufferLambdaHandler/makeHandler(context:)`` or ``LambdaHandler/init(context:)`` /// as an argument. - public struct InitializationContext { + public struct InitializationContext: _AWSLambdaSendable { /// `Logger` to log with /// /// - note: The `LogLevel` can be configured using the `LOG_LEVEL` environment variable. @@ -67,17 +73,17 @@ extension Lambda { /// Lambda runtime context. /// The Lambda runtime generates and passes the `Context` to the Lambda handler as an argument. -public struct LambdaContext: CustomDebugStringConvertible { - final class _Storage { - var requestID: String - var traceID: String - var invokedFunctionARN: String - var deadline: DispatchWallTime - var cognitoIdentity: String? - var clientContext: String? - var logger: Logger - var eventLoop: EventLoop - var allocator: ByteBufferAllocator +public struct LambdaContext: CustomDebugStringConvertible, _AWSLambdaSendable { + final class _Storage: _AWSLambdaSendable { + let requestID: String + let traceID: String + let invokedFunctionARN: String + let deadline: DispatchWallTime + let cognitoIdentity: String? + let clientContext: String? + let logger: Logger + let eventLoop: EventLoop + let allocator: ByteBufferAllocator init( requestID: String, diff --git a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift index 76d35af2..af2d9507 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift @@ -26,7 +26,7 @@ import NIOCore /// level protocols ``EventLoopLambdaHandler`` and /// ``ByteBufferLambdaHandler``. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) -public protocol LambdaHandler: EventLoopLambdaHandler { +public protocol LambdaHandler: EventLoopLambdaHandler where Event: _AWSLambdaSendable { /// The Lambda initialization method /// Use this method to initialize resources that will be used in every request. /// @@ -157,7 +157,7 @@ extension EventLoopLambdaHandler where Output == Void { /// - note: This is a low level protocol designed to power the higher level ``EventLoopLambdaHandler`` and /// ``LambdaHandler`` based APIs. /// Most users are not expected to use this protocol. -public protocol ByteBufferLambdaHandler { +public protocol ByteBufferLambdaHandler: _ByteBufferLambdaHandlerSendable { /// Create your Lambda handler for the runtime. /// /// Use this to initialize all your resources that you want to cache between invocations. This could be database diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift index 0619dfa1..aba9d507 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift @@ -189,3 +189,8 @@ public final class LambdaRuntime { } } } + +// TODO: ideally this would not be @unchecked Sendable, but Sendable checks do not understand locks +#if compiler(>=5.5) && canImport(_Concurrency) +extension LambdaRuntime: @unchecked Sendable {} +#endif diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClient.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClient.swift index 7303ef1c..ddb946a5 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClient.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClient.swift @@ -13,8 +13,13 @@ //===----------------------------------------------------------------------===// import Logging +#if compiler(>=5.6) +@preconcurrency import NIOCore +@preconcurrency import NIOHTTP1 +#else import NIOCore import NIOHTTP1 +#endif /// An HTTP based client for AWS Runtime Engine. This encapsulates the RESTful methods exposed by the Runtime Engine: /// * /runtime/invocation/next diff --git a/Sources/AWSLambdaRuntimeCore/Sendable.swift b/Sources/AWSLambdaRuntimeCore/Sendable.swift new file mode 100644 index 00000000..3c4d1c40 --- /dev/null +++ b/Sources/AWSLambdaRuntimeCore/Sendable.swift @@ -0,0 +1,27 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +// Sendable bridging types + +#if compiler(>=5.6) +@preconcurrency public protocol _ByteBufferLambdaHandlerSendable: Sendable {} +#else +public protocol _ByteBufferLambdaHandlerSendable {} +#endif + +#if compiler(>=5.6) +public typealias _AWSLambdaSendable = Sendable +#else +public typealias _AWSLambdaSendable = Any +#endif diff --git a/Sources/AWSLambdaRuntimeCore/Terminator.swift b/Sources/AWSLambdaRuntimeCore/Terminator.swift index 9ad62d3a..dceccefc 100644 --- a/Sources/AWSLambdaRuntimeCore/Terminator.swift +++ b/Sources/AWSLambdaRuntimeCore/Terminator.swift @@ -18,7 +18,7 @@ import NIOCore /// Lambda terminator. /// Utility to manage the lambda shutdown sequence. public final class LambdaTerminator { - private typealias Handler = (EventLoop) -> EventLoopFuture + fileprivate typealias Handler = (EventLoop) -> EventLoopFuture private var storage: Storage @@ -99,7 +99,7 @@ extension LambdaTerminator { } extension LambdaTerminator { - private final class Storage { + fileprivate final class Storage { private let lock: Lock private var index: [RegistrationKey] private var map: [RegistrationKey: (name: String, handler: Handler)] @@ -137,3 +137,9 @@ extension LambdaTerminator { let underlying: [Error] } } + +// TODO: ideally this would not be @unchecked Sendable, but Sendable checks do not understand locks +#if compiler(>=5.5) && canImport(_Concurrency) +extension LambdaTerminator: @unchecked Sendable {} +extension LambdaTerminator.Storage: @unchecked Sendable {} +#endif diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift index 3da730f0..a5fd7daf 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift @@ -13,9 +13,14 @@ //===----------------------------------------------------------------------===// @testable import AWSLambdaRuntimeCore +#if compiler(>=5.6) +@preconcurrency import Logging +@preconcurrency import NIOPosix +#else import Logging -import NIOCore import NIOPosix +#endif +import NIOCore import XCTest class LambdaTest: XCTestCase { @@ -250,6 +255,47 @@ class LambdaTest: XCTestCase { XCTAssertLessThanOrEqual(context.getRemainingTime(), .seconds(1)) XCTAssertGreaterThan(context.getRemainingTime(), .milliseconds(800)) } + + #if compiler(>=5.6) + func testSendable() async throws { + struct Handler: EventLoopLambdaHandler { + typealias Event = String + typealias Output = String + + static func makeHandler(context: Lambda.InitializationContext) -> EventLoopFuture { + context.eventLoop.makeSucceededFuture(Handler()) + } + + func handle(_ event: String, context: LambdaContext) -> EventLoopFuture { + context.eventLoop.makeSucceededFuture("hello") + } + } + + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } + + let server = try MockLambdaServer(behavior: Behavior()).start().wait() + defer { XCTAssertNoThrow(try server.stop().wait()) } + + let logger = Logger(label: "TestLogger") + let configuration = Lambda.Configuration(runtimeEngine: .init(requestTimeout: .milliseconds(100))) + + let handler1 = Handler() + let task = Task.detached { + print(configuration.description) + logger.info("hello") + let runner = Lambda.Runner(eventLoop: eventLoopGroup.next(), configuration: configuration) + + try runner.run(logger: logger, handler: handler1).wait() + + try runner.initialize(logger: logger, terminator: LambdaTerminator(), handlerType: Handler.self).flatMap { handler2 in + runner.run(logger: logger, handler: handler2) + }.wait() + } + + try await task.value + } + #endif } private struct Behavior: LambdaServerBehavior { diff --git a/docker/Dockerfile b/docker/Dockerfile index 9979f708..fdd8b74f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -15,7 +15,7 @@ RUN echo 'export PATH="$HOME/.tools:$PATH"' >> $HOME/.profile # swiftformat (until part of the toolchain) -ARG swiftformat_version=0.47.3 +ARG swiftformat_version=0.49.6 RUN git clone --branch $swiftformat_version --depth 1 https://github.com/nicklockwood/SwiftFormat $HOME/.tools/swift-format RUN cd $HOME/.tools/swift-format && swift build -c release RUN ln -s $HOME/.tools/swift-format/.build/release/swiftformat $HOME/.tools/swiftformat From bb34ac69cb2655e3efc50ea15f04a136586b2ec2 Mon Sep 17 00:00:00 2001 From: tom doron Date: Tue, 12 Apr 2022 18:26:38 -0700 Subject: [PATCH 2/5] fixup --- .../AWSLambdaRuntimeCore/LambdaHandler.swift | 20 +++++++++++++++++-- Sources/AWSLambdaRuntimeCore/Sendable.swift | 6 ------ docker/Dockerfile | 2 +- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift index af2d9507..aa4a95aa 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift @@ -58,13 +58,29 @@ extension LambdaHandler { public func handle(_ event: Event, context: LambdaContext) -> EventLoopFuture { let promise = context.eventLoop.makePromise(of: Output.self) + // using an unchecked sendable wrapper for the handler + // this is safe since lambda runtime is designed to calls the handler serially + let handler = UncheckedSendableHandler(underlying: self) promise.completeWithTask { - try await self.handle(event, context: context) + try await handler.handle(event, context: context) } return promise.futureResult } } +/// unchecked sendable wrapper for the handler +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +fileprivate struct UncheckedSendableHandler: @unchecked Sendable { + let underlying: Underlying + + init(underlying: Underlying) { + self.underlying = underlying + } + + func handle(_ event: Underlying.Event, context: LambdaContext) async throws -> Underlying.Output { + try await self.underlying.handle(event, context: context) + } +} #endif // MARK: - EventLoopLambdaHandler @@ -157,7 +173,7 @@ extension EventLoopLambdaHandler where Output == Void { /// - note: This is a low level protocol designed to power the higher level ``EventLoopLambdaHandler`` and /// ``LambdaHandler`` based APIs. /// Most users are not expected to use this protocol. -public protocol ByteBufferLambdaHandler: _ByteBufferLambdaHandlerSendable { +public protocol ByteBufferLambdaHandler { /// Create your Lambda handler for the runtime. /// /// Use this to initialize all your resources that you want to cache between invocations. This could be database diff --git a/Sources/AWSLambdaRuntimeCore/Sendable.swift b/Sources/AWSLambdaRuntimeCore/Sendable.swift index 3c4d1c40..936403e4 100644 --- a/Sources/AWSLambdaRuntimeCore/Sendable.swift +++ b/Sources/AWSLambdaRuntimeCore/Sendable.swift @@ -14,12 +14,6 @@ // Sendable bridging types -#if compiler(>=5.6) -@preconcurrency public protocol _ByteBufferLambdaHandlerSendable: Sendable {} -#else -public protocol _ByteBufferLambdaHandlerSendable {} -#endif - #if compiler(>=5.6) public typealias _AWSLambdaSendable = Sendable #else diff --git a/docker/Dockerfile b/docker/Dockerfile index fdd8b74f..9979f708 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -15,7 +15,7 @@ RUN echo 'export PATH="$HOME/.tools:$PATH"' >> $HOME/.profile # swiftformat (until part of the toolchain) -ARG swiftformat_version=0.49.6 +ARG swiftformat_version=0.47.3 RUN git clone --branch $swiftformat_version --depth 1 https://github.com/nicklockwood/SwiftFormat $HOME/.tools/swift-format RUN cd $HOME/.tools/swift-format && swift build -c release RUN ln -s $HOME/.tools/swift-format/.build/release/swiftformat $HOME/.tools/swiftformat From 297788bfd4b07d5f601f1cd0ea2f3748eb70e4c2 Mon Sep 17 00:00:00 2001 From: tom doron Date: Wed, 13 Apr 2022 12:16:47 -0700 Subject: [PATCH 3/5] fixup --- Sources/AWSLambdaRuntimeCore/LambdaHandler.swift | 7 ++++--- Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift | 5 ----- Sources/AWSLambdaRuntimeCore/Terminator.swift | 3 ++- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift index aa4a95aa..8bb61179 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift @@ -26,7 +26,7 @@ import NIOCore /// level protocols ``EventLoopLambdaHandler`` and /// ``ByteBufferLambdaHandler``. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) -public protocol LambdaHandler: EventLoopLambdaHandler where Event: _AWSLambdaSendable { +public protocol LambdaHandler: EventLoopLambdaHandler { /// The Lambda initialization method /// Use this method to initialize resources that will be used in every request. /// @@ -69,15 +69,16 @@ extension LambdaHandler { } /// unchecked sendable wrapper for the handler +/// this is safe since lambda runtime is designed to calls the handler serially @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) -fileprivate struct UncheckedSendableHandler: @unchecked Sendable { +fileprivate struct UncheckedSendableHandler: @unchecked Sendable where Event == Underlying.Event, Output == Underlying.Output { let underlying: Underlying init(underlying: Underlying) { self.underlying = underlying } - func handle(_ event: Underlying.Event, context: LambdaContext) async throws -> Underlying.Output { + func handle(_ event: Event, context: LambdaContext) async throws -> Output { try await self.underlying.handle(event, context: context) } } diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift index aba9d507..0619dfa1 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift @@ -189,8 +189,3 @@ public final class LambdaRuntime { } } } - -// TODO: ideally this would not be @unchecked Sendable, but Sendable checks do not understand locks -#if compiler(>=5.5) && canImport(_Concurrency) -extension LambdaRuntime: @unchecked Sendable {} -#endif diff --git a/Sources/AWSLambdaRuntimeCore/Terminator.swift b/Sources/AWSLambdaRuntimeCore/Terminator.swift index dceccefc..bd1737e0 100644 --- a/Sources/AWSLambdaRuntimeCore/Terminator.swift +++ b/Sources/AWSLambdaRuntimeCore/Terminator.swift @@ -138,7 +138,8 @@ extension LambdaTerminator { } } -// TODO: ideally this would not be @unchecked Sendable, but Sendable checks do not understand locks +// Ideally this would not be @unchecked Sendable, but Sendable checks do not understand locks +// We can transition this to an actor once we drop support for older Swift versions #if compiler(>=5.5) && canImport(_Concurrency) extension LambdaTerminator: @unchecked Sendable {} extension LambdaTerminator.Storage: @unchecked Sendable {} From e818ad952e8002d411d39234d326ceb706541412 Mon Sep 17 00:00:00 2001 From: tom doron Date: Wed, 13 Apr 2022 17:04:22 -0700 Subject: [PATCH 4/5] LambdaRuntime --- .../AWSLambdaRuntimeCore/LambdaRuntime.swift | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift index 0619dfa1..b279cea1 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift @@ -63,10 +63,17 @@ public final class LambdaRuntime { /// Start the `LambdaRuntime`. /// - /// - Returns: An `EventLoopFuture` that is fulfilled after the Lambda hander has been created and initiliazed, and a first run has been scheduled. - /// - /// - note: This method must be called on the `EventLoop` the `LambdaRuntime` has been initialized with. + /// - Returns: An `EventLoopFuture` that is fulfilled after the Lambda hander has been created and initialized, and a first run has been scheduled. public func start() -> EventLoopFuture { + if self.eventLoop.inEventLoop { + return self._start() + } else { + return self.eventLoop.flatSubmit { self._start() } + } + } + + private func _start() -> EventLoopFuture { + // This method must be called on the `EventLoop` the `LambdaRuntime` has been initialized with. self.eventLoop.assertInEventLoop() logger.info("lambda runtime starting with \(self.configuration)") @@ -189,3 +196,8 @@ public final class LambdaRuntime { } } } + +/// This is safe since lambda runtime is intended to be used within a single `EventLoop` +#if compiler(>=5.5) && canImport(_Concurrency) +extension LambdaRuntime: @unchecked Sendable {} +#endif From c432ea773ebc615fa86b46addcdbaaa3c6ff7910 Mon Sep 17 00:00:00 2001 From: tomer doron Date: Thu, 14 Apr 2022 09:05:25 -0700 Subject: [PATCH 5/5] Update Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift Co-authored-by: Fabian Fett --- Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift index b279cea1..19057e14 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift @@ -197,7 +197,7 @@ public final class LambdaRuntime { } } -/// This is safe since lambda runtime is intended to be used within a single `EventLoop` +/// This is safe since lambda runtime synchronizes by dispatching all methods to a single `EventLoop` #if compiler(>=5.5) && canImport(_Concurrency) extension LambdaRuntime: @unchecked Sendable {} #endif