From cb324e02b9deeec31314dd17e6d7d97f9b950681 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Sat, 2 May 2020 12:32:05 +0200 Subject: [PATCH 1/6] Use NIO in Single Threaded Mode --- Package.swift | 2 +- Sources/AWSLambdaRuntimeCore/Lambda.swift | 40 ++++++++++--------- .../LambdaTest.swift | 2 + 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Package.swift b/Package.swift index 94353929..17193782 100644 --- a/Package.swift +++ b/Package.swift @@ -18,7 +18,7 @@ let package = Package( .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", from: "2.8.0"), + .package(url: "https://github.com/weissi/swift-nio.git", .branch("jw-single-threaded-nio")), .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), .package(url: "https://github.com/swift-server/swift-backtrace.git", from: "1.1.0"), ], diff --git a/Sources/AWSLambdaRuntimeCore/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift index 51ab06c6..1515c2e6 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda.swift @@ -92,29 +92,33 @@ public enum Lambda { // for testing and internal use @discardableResult internal static func run(configuration: Configuration = .init(), factory: @escaping HandlerFactory) -> Result { - do { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) // only need one thread, will improve performance - defer { try! eventLoopGroup.syncShutdownGracefully() } - let result = try self.runAsync(eventLoopGroup: eventLoopGroup, configuration: configuration, factory: factory).wait() - return .success(result) - } catch { - return .failure(error) - } - } - - internal static func runAsync(eventLoopGroup: EventLoopGroup, configuration: Configuration, factory: @escaping HandlerFactory) -> EventLoopFuture { Backtrace.install() var logger = Logger(label: "Lambda") logger.logLevel = configuration.general.logLevel - let lifecycle = Lifecycle(eventLoop: eventLoopGroup.next(), logger: logger, configuration: configuration, factory: factory) - let signalSource = trap(signal: configuration.lifecycle.stopSignal) { signal in - logger.info("intercepted signal: \(signal)") - lifecycle.shutdown() - } - return lifecycle.start().flatMap { - return lifecycle.shutdownFuture.always { _ in + + var r: Result? + MultiThreadedEventLoopGroup.withCurrentThreadAsEventLoop { eventLoop in + let lifecycle = Lifecycle(eventLoop: eventLoop, logger: logger, configuration: configuration, factory: factory) + let signalSource = trap(signal: configuration.lifecycle.stopSignal) { signal in + logger.info("intercepted signal: \(signal)") + lifecycle.shutdown() + } + + _ = lifecycle.start().flatMap { + lifecycle.shutdownFuture + } + .always { result in signalSource.cancel() + eventLoop.shutdownGracefully { _ in + logger.info("shutdown") + } + + r = result } } + + logger.info("shutdown completed") + + return r! } } diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift index 60cc2e9a..244eb679 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift @@ -128,6 +128,7 @@ class LambdaTest: XCTestCase { assertLambdaLifecycleResult(result, shouldFailWithError: TestError("kaboom")) } + #if false func testStartStop() { let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) @@ -150,6 +151,7 @@ class LambdaTest: XCTestCase { } XCTAssertNoThrow(try future.wait()) } + #endif func testTimeout() { let timeout: Int64 = 100 From 3e969d601ef45207d7949584d5c1de3081d34e20 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Mon, 4 May 2020 14:08:46 +0200 Subject: [PATCH 2/6] Offload sync lambda factory --- Sources/AWSLambdaRuntimeCore/Lambda.swift | 13 ++++++++----- Sources/AWSLambdaRuntimeCore/LambdaHandler.swift | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Sources/AWSLambdaRuntimeCore/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift index 1515c2e6..c814d21d 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda.swift @@ -80,12 +80,15 @@ public enum Lambda { @discardableResult internal static func run(configuration: Configuration = .init(), factory: @escaping (EventLoop) throws -> Handler) -> Result { self.run(configuration: configuration, factory: { eventloop -> EventLoopFuture in - do { - let handler = try factory(eventloop) - return eventloop.makeSucceededFuture(handler) - } catch { - return eventloop.makeFailedFuture(error) + let promise = eventloop.makePromise(of: Handler.self) + Lambda.defaultOffloadQueue.async { + do { + promise.succeed(try factory(eventloop)) + } catch { + promise.fail(error) + } } + return promise.futureResult }) } diff --git a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift index 9294e4f5..8e44792d 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift @@ -39,7 +39,7 @@ public protocol LambdaHandler: EventLoopLambdaHandler { func handle(context: Lambda.Context, payload: In, callback: @escaping (Result) -> Void) } -private extension Lambda { +internal extension Lambda { static let defaultOffloadQueue = DispatchQueue(label: "LambdaHandler.offload") } From ad13563775ebd24857464ca6b10175cc6e259963 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Tue, 12 May 2020 16:57:31 +0200 Subject: [PATCH 3/6] Fix test --- Package.swift | 6 +++--- Sources/AWSLambdaRuntimeCore/Lambda.swift | 2 ++ .../LambdaTest.swift | 20 +++++++++---------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Package.swift b/Package.swift index 17193782..44e2434f 100644 --- a/Package.swift +++ b/Package.swift @@ -18,9 +18,9 @@ let package = Package( .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), ], dependencies: [ - .package(url: "https://github.com/weissi/swift-nio.git", .branch("jw-single-threaded-nio")), - .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), - .package(url: "https://github.com/swift-server/swift-backtrace.git", from: "1.1.0"), + .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.17.0")), + .package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.0.0")), + .package(url: "https://github.com/swift-server/swift-backtrace.git", .upToNextMajor(from: "1.1.0")), ], targets: [ .target(name: "AWSLambdaRuntime", dependencies: [ diff --git a/Sources/AWSLambdaRuntimeCore/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift index c814d21d..b97e56bc 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda.swift @@ -81,6 +81,8 @@ public enum Lambda { internal static func run(configuration: Configuration = .init(), factory: @escaping (EventLoop) throws -> Handler) -> Result { self.run(configuration: configuration, factory: { eventloop -> EventLoopFuture in let promise = eventloop.makePromise(of: Handler.self) + // if we have a callback based handler factory, we offload the creation of the handler + // onto the default offload queue, to ensure that the eventloop is never blocked. Lambda.defaultOffloadQueue.async { do { promise.succeed(try factory(eventloop)) diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift index 244eb679..764f7bcf 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift @@ -128,8 +128,7 @@ class LambdaTest: XCTestCase { assertLambdaLifecycleResult(result, shouldFailWithError: TestError("kaboom")) } - #if false - func testStartStop() { + func testStartStopInDebugMode() { let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } @@ -137,21 +136,22 @@ class LambdaTest: XCTestCase { let signal = Signal.ALRM let maxTimes = 1000 let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes, stopSignal: signal)) - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } - let future = Lambda.runAsync(eventLoopGroup: eventLoopGroup, configuration: configuration, factory: { $0.makeSucceededFuture(EchoHandler()) }) DispatchQueue(label: "test").async { + // we need to schedule the signal before we start the long running `Lambda.run`, since + // `Lambda.run` will block the main thread. usleep(100_000) kill(getpid(), signal.rawValue) } - future.whenSuccess { result in - XCTAssertGreaterThan(result, 0, "should have stopped before any request made") - XCTAssertLessThan(result, maxTimes, "should have stopped before \(maxTimes)") + let result = Lambda.run(configuration: configuration, factory: { $0.makeSucceededFuture(EchoHandler()) }) + + guard case .success(let invocationCount) = result else { + return XCTFail("expected to have not failed") } - XCTAssertNoThrow(try future.wait()) + + XCTAssertGreaterThan(invocationCount, 0, "should have stopped before any request made") + XCTAssertLessThan(invocationCount, maxTimes, "should have stopped before \(maxTimes)") } - #endif func testTimeout() { let timeout: Int64 = 100 From cc96a1be39359e9498692c85c8b8bdfceab2aa5f Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Tue, 12 May 2020 20:39:35 +0200 Subject: [PATCH 4/6] Fixed pr notes. --- Sources/AWSLambdaRuntimeCore/Lambda.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Sources/AWSLambdaRuntimeCore/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift index b97e56bc..3f9db288 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda.swift @@ -101,7 +101,7 @@ public enum Lambda { var logger = Logger(label: "Lambda") logger.logLevel = configuration.general.logLevel - var r: Result? + var result: Result! MultiThreadedEventLoopGroup.withCurrentThreadAsEventLoop { eventLoop in let lifecycle = Lifecycle(eventLoop: eventLoop, logger: logger, configuration: configuration, factory: factory) let signalSource = trap(signal: configuration.lifecycle.stopSignal) { signal in @@ -112,18 +112,20 @@ public enum Lambda { _ = lifecycle.start().flatMap { lifecycle.shutdownFuture } - .always { result in + .always { lifecycleResult in signalSource.cancel() - eventLoop.shutdownGracefully { _ in + eventLoop.shutdownGracefully { error in + if let error = error { + preconditionFailure("Failed to shutdown eventloop: \(error)") + } logger.info("shutdown") } - r = result + result = lifecycleResult } } logger.info("shutdown completed") - - return r! + return result } } From f96746082af7cafaf54d869cff8e0de2532bb091 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Tue, 12 May 2020 22:24:36 +0200 Subject: [PATCH 5/6] Fixed another pr comment --- Sources/AWSLambdaRuntimeCore/Lambda.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/AWSLambdaRuntimeCore/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift index 3f9db288..65ea56b4 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda.swift @@ -109,10 +109,10 @@ public enum Lambda { lifecycle.shutdown() } - _ = lifecycle.start().flatMap { + lifecycle.start().flatMap { lifecycle.shutdownFuture } - .always { lifecycleResult in + .whenComplete { lifecycleResult in signalSource.cancel() eventLoop.shutdownGracefully { error in if let error = error { From a8610a75212457f115c54050d006783bfe94a6e7 Mon Sep 17 00:00:00 2001 From: tomer doron Date: Tue, 12 May 2020 13:39:47 -0700 Subject: [PATCH 6/6] mixup --- Sources/AWSLambdaRuntimeCore/Lambda.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/AWSLambdaRuntimeCore/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift index 65ea56b4..bf552b55 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda.swift @@ -111,16 +111,13 @@ public enum Lambda { lifecycle.start().flatMap { lifecycle.shutdownFuture - } - .whenComplete { lifecycleResult in + }.whenComplete { lifecycleResult in signalSource.cancel() eventLoop.shutdownGracefully { error in if let error = error { preconditionFailure("Failed to shutdown eventloop: \(error)") } - logger.info("shutdown") } - result = lifecycleResult } }