Skip to content

Commit 9f976d7

Browse files
committed
Added ServiceLifecycle dependency and started integration
1 parent a5ef4e9 commit 9f976d7

File tree

7 files changed

+79
-15
lines changed

7 files changed

+79
-15
lines changed

Package.swift

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ let package = Package(
1818
.package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.17.0")),
1919
.package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.0.0")),
2020
.package(url: "https://github.com/swift-server/swift-backtrace.git", .upToNextMajor(from: "1.1.0")),
21+
// .package(url: "https://github.com/swift-server/swift-service-lifecycle.git", .upToNextMajor(from: "1.0.0-alpha")),
22+
.package(url: "https://github.com/swift-server/swift-service-lifecycle.git", .branch("master")),
2123
],
2224
targets: [
2325
.target(name: "AWSLambdaRuntime", dependencies: [
@@ -29,6 +31,7 @@ let package = Package(
2931
.product(name: "Logging", package: "swift-log"),
3032
.product(name: "Backtrace", package: "swift-backtrace"),
3133
.product(name: "NIOHTTP1", package: "swift-nio"),
34+
.product(name: "Lifecycle", package: "swift-service-lifecycle"),
3235
]),
3336
.testTarget(name: "AWSLambdaRuntimeCoreTests", dependencies: [
3437
.byName(name: "AWSLambdaRuntimeCore"),

Sources/AWSLambdaRuntimeCore/Lambda.swift

+15-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import Darwin.C
2020

2121
import Backtrace
2222
import Logging
23+
import Lifecycle
2324
import NIO
2425

2526
public enum Lambda {
@@ -101,13 +102,15 @@ public enum Lambda {
101102
// for testing and internal use
102103
internal static func run(configuration: Configuration = .init(), factory: @escaping HandlerFactory) -> Result<Int, Error> {
103104
let _run = { (configuration: Configuration, factory: @escaping HandlerFactory) -> Result<Int, Error> in
104-
Backtrace.install()
105105
var logger = Logger(label: "Lambda")
106106
logger.logLevel = configuration.general.logLevel
107+
108+
// we don't intercept the shutdown signal here yet.
109+
let serviceLifecycle = ServiceLifecycle(configuration: .init(logger: logger, shutdownSignal: [], installBacktrace: true))
107110

108111
var result: Result<Int, Error>!
109112
MultiThreadedEventLoopGroup.withCurrentThreadAsEventLoop { eventLoop in
110-
let lifecycle = Lifecycle(eventLoop: eventLoop, logger: logger, configuration: configuration, factory: factory)
113+
let lifecycle = Lifecycle(eventLoop: eventLoop, serviceLifecycle: serviceLifecycle, logger: logger, configuration: configuration, factory: factory)
111114
#if DEBUG
112115
let signalSource = trap(signal: configuration.lifecycle.stopSignal) { signal in
113116
logger.info("intercepted signal: \(signal)")
@@ -121,11 +124,19 @@ public enum Lambda {
121124
#if DEBUG
122125
signalSource.cancel()
123126
#endif
124-
eventLoop.shutdownGracefully { error in
127+
128+
serviceLifecycle.shutdown { (error) in
125129
if let error = error {
126-
preconditionFailure("Failed to shutdown eventloop: \(error)")
130+
preconditionFailure("Failed to shutdown service: \(error)")
131+
}
132+
133+
eventLoop.shutdownGracefully { error in
134+
if let error = error {
135+
preconditionFailure("Failed to shutdown eventloop: \(error)")
136+
}
127137
}
128138
}
139+
129140
result = lifecycleResult
130141
}
131142
}

Sources/AWSLambdaRuntimeCore/LambdaContext.swift

+6-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import Dispatch
16+
import Lifecycle
1617
import Logging
1718
import NIO
1819

@@ -32,13 +33,17 @@ extension Lambda {
3233
/// - note: The `EventLoop` is shared with the Lambda runtime engine and should be handled with extra care.
3334
/// Most importantly the `EventLoop` must never be blocked.
3435
public let eventLoop: EventLoop
36+
37+
/// `ServiceLifecycle` to register services with
38+
public let serviceLifecycle: ServiceLifecycle
3539

3640
/// `ByteBufferAllocator` to allocate `ByteBuffer`
3741
public let allocator: ByteBufferAllocator
3842

39-
internal init(logger: Logger, eventLoop: EventLoop, allocator: ByteBufferAllocator) {
43+
internal init(logger: Logger, eventLoop: EventLoop, serviceLifecycle: ServiceLifecycle, allocator: ByteBufferAllocator) {
4044
self.eventLoop = eventLoop
4145
self.logger = logger
46+
self.serviceLifecycle = serviceLifecycle
4247
self.allocator = allocator
4348
}
4449
}

Sources/AWSLambdaRuntimeCore/LambdaLifecycle.swift

+7-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import Logging
16+
import Lifecycle
1617
import NIO
1718
import NIOConcurrencyHelpers
1819

@@ -22,6 +23,7 @@ extension Lambda {
2223
/// - note: It is intended to be used within a single `EventLoop`. For this reason this class is not thread safe.
2324
public final class Lifecycle {
2425
private let eventLoop: EventLoop
26+
private let serviceLifecycle: ServiceLifecycle
2527
private let shutdownPromise: EventLoopPromise<Int>
2628
private let logger: Logger
2729
private let configuration: Configuration
@@ -40,12 +42,13 @@ extension Lambda {
4042
/// - eventLoop: An `EventLoop` to run the Lambda on.
4143
/// - logger: A `Logger` to log the Lambda events.
4244
/// - factory: A `LambdaHandlerFactory` to create the concrete Lambda handler.
43-
public convenience init(eventLoop: EventLoop, logger: Logger, factory: @escaping HandlerFactory) {
44-
self.init(eventLoop: eventLoop, logger: logger, configuration: .init(), factory: factory)
45+
public convenience init(eventLoop: EventLoop, serviceLifecycle: ServiceLifecycle, logger: Logger, factory: @escaping HandlerFactory) {
46+
self.init(eventLoop: eventLoop, serviceLifecycle: serviceLifecycle, logger: logger, configuration: .init(), factory: factory)
4547
}
4648

47-
init(eventLoop: EventLoop, logger: Logger, configuration: Configuration, factory: @escaping HandlerFactory) {
49+
init(eventLoop: EventLoop, serviceLifecycle: ServiceLifecycle, logger: Logger, configuration: Configuration, factory: @escaping HandlerFactory) {
4850
self.eventLoop = eventLoop
51+
self.serviceLifecycle = serviceLifecycle
4952
self.shutdownPromise = eventLoop.makePromise(of: Int.self)
5053
self.logger = logger
5154
self.configuration = configuration
@@ -80,7 +83,7 @@ extension Lambda {
8083
logger[metadataKey: "lifecycleId"] = .string(self.configuration.lifecycle.id)
8184
let runner = Runner(eventLoop: self.eventLoop, configuration: self.configuration)
8285

83-
let startupFuture = runner.initialize(logger: logger, factory: self.factory)
86+
let startupFuture = runner.initialize(serviceLifecycle: self.serviceLifecycle, logger: logger, factory: self.factory)
8487
startupFuture.flatMap { handler -> EventLoopFuture<(ByteBufferLambdaHandler, Result<Int, Error>)> in
8588
// after the startup future has succeeded, we have a handler that we can use
8689
// to `run` the lambda.

Sources/AWSLambdaRuntimeCore/LambdaRunner.swift

+17-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import Dispatch
1616
import Logging
17+
import Lifecycle
1718
import NIO
1819

1920
extension Lambda {
@@ -34,19 +35,34 @@ extension Lambda {
3435
/// Run the user provided initializer. This *must* only be called once.
3536
///
3637
/// - Returns: An `EventLoopFuture<LambdaHandler>` fulfilled with the outcome of the initialization.
37-
func initialize(logger: Logger, factory: @escaping HandlerFactory) -> EventLoopFuture<Handler> {
38+
func initialize(serviceLifecycle: ServiceLifecycle, logger: Logger, factory: @escaping HandlerFactory) -> EventLoopFuture<Handler> {
3839
logger.debug("initializing lambda")
3940
// 1. create the handler from the factory
4041
// 2. report initialization error if one occured
4142
let context = InitializationContext(logger: logger,
4243
eventLoop: self.eventLoop,
44+
serviceLifecycle: serviceLifecycle,
4345
allocator: self.allocator)
4446
return factory(context)
4547
// Hopping back to "our" EventLoop is important in case the factory returns a future
4648
// that originated from a foreign EventLoop/EventLoopGroup.
4749
// This can happen if the factory uses a library (let's say a database client) that manages its own threads/loops
4850
// for whatever reason and returns a future that originated from that foreign EventLoop.
4951
.hop(to: self.eventLoop)
52+
.flatMap { (handler) in
53+
let promise = self.eventLoop.makePromise(of: ByteBufferLambdaHandler.self)
54+
// after we have created the LambdaHandler we must now start the services.
55+
// in order to not have to map once our success case returns the handler.
56+
serviceLifecycle.start { (error) in
57+
if let error = error {
58+
promise.fail(error)
59+
}
60+
else {
61+
promise.succeed(handler)
62+
}
63+
}
64+
return promise.futureResult
65+
}
5066
.peekError { error in
5167
self.runtimeClient.reportInitializationError(logger: logger, error: error).peekError { reportingError in
5268
// We're going to bail out because the init failed, so there's not a lot we can do other than log

Tests/AWSLambdaRuntimeCoreTests/LambdaLifecycleTest.swift

+22-4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
@testable import AWSLambdaRuntimeCore
1616
import Logging
17+
import Lifecycle
1718
import NIO
1819
import NIOHTTP1
1920
import XCTest
@@ -25,11 +26,16 @@ class LambdaLifecycleTest: XCTestCase {
2526
defer { XCTAssertNoThrow(try server.stop().wait()) }
2627
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
2728
defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) }
28-
29+
30+
let serviceLifecycle = ServiceLifecycle(configuration: .init(shutdownSignal: [], installBacktrace: false))
31+
defer {
32+
serviceLifecycle.shutdown()
33+
serviceLifecycle.wait()
34+
}
2935
let eventLoop = eventLoopGroup.next()
3036
let logger = Logger(label: "TestLogger")
3137
let testError = TestError("kaboom")
32-
let lifecycle = Lambda.Lifecycle(eventLoop: eventLoop, logger: logger, factory: {
38+
let lifecycle = Lambda.Lifecycle(eventLoop: eventLoop, serviceLifecycle: serviceLifecycle, logger: logger, factory: {
3339
$0.eventLoop.makeFailedFuture(testError)
3440
})
3541

@@ -68,6 +74,12 @@ class LambdaLifecycleTest: XCTestCase {
6874
defer { XCTAssertNoThrow(try server.stop().wait()) }
6975
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
7076
defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) }
77+
78+
let serviceLifecycle = ServiceLifecycle(configuration: .init(shutdownSignal: [], installBacktrace: false))
79+
defer {
80+
serviceLifecycle.shutdown()
81+
serviceLifecycle.wait()
82+
}
7183

7284
var count = 0
7385
let handler = CallbackLambdaHandler({ XCTFail("Should not be reached"); return $0.eventLoop.makeSucceededFuture($1) }) { context in
@@ -77,7 +89,7 @@ class LambdaLifecycleTest: XCTestCase {
7789

7890
let eventLoop = eventLoopGroup.next()
7991
let logger = Logger(label: "TestLogger")
80-
let lifecycle = Lambda.Lifecycle(eventLoop: eventLoop, logger: logger, factory: {
92+
let lifecycle = Lambda.Lifecycle(eventLoop: eventLoop, serviceLifecycle: serviceLifecycle, logger: logger, factory: {
8193
$0.eventLoop.makeSucceededFuture(handler)
8294
})
8395

@@ -94,6 +106,12 @@ class LambdaLifecycleTest: XCTestCase {
94106
defer { XCTAssertNoThrow(try server.stop().wait()) }
95107
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
96108
defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) }
109+
110+
let serviceLifecycle = ServiceLifecycle(configuration: .init(shutdownSignal: [], installBacktrace: false))
111+
defer {
112+
serviceLifecycle.shutdown()
113+
serviceLifecycle.wait()
114+
}
97115

98116
var count = 0
99117
let handler = CallbackLambdaHandler({ XCTFail("Should not be reached"); return $0.eventLoop.makeSucceededFuture($1) }) { context in
@@ -103,7 +121,7 @@ class LambdaLifecycleTest: XCTestCase {
103121

104122
let eventLoop = eventLoopGroup.next()
105123
let logger = Logger(label: "TestLogger")
106-
let lifecycle = Lambda.Lifecycle(eventLoop: eventLoop, logger: logger, factory: {
124+
let lifecycle = Lambda.Lifecycle(eventLoop: eventLoop, serviceLifecycle: serviceLifecycle, logger: logger, factory: {
107125
$0.eventLoop.makeSucceededFuture(handler)
108126
})
109127

Tests/AWSLambdaRuntimeCoreTests/Utils.swift

+9-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
@testable import AWSLambdaRuntimeCore
1616
import Logging
17+
import Lifecycle
1718
import NIO
1819
import XCTest
1920

@@ -29,7 +30,14 @@ func runLambda(behavior: LambdaServerBehavior, factory: @escaping Lambda.Handler
2930
let runner = Lambda.Runner(eventLoop: eventLoopGroup.next(), configuration: configuration)
3031
let server = try MockLambdaServer(behavior: behavior).start().wait()
3132
defer { XCTAssertNoThrow(try server.stop().wait()) }
32-
try runner.initialize(logger: logger, factory: factory).flatMap { handler in
33+
34+
let serviceLifecycle = ServiceLifecycle(configuration: .init(shutdownSignal: [], installBacktrace: false))
35+
defer {
36+
serviceLifecycle.shutdown()
37+
serviceLifecycle.wait()
38+
}
39+
40+
try runner.initialize(serviceLifecycle: serviceLifecycle, logger: logger, factory: factory).flatMap { handler in
3341
runner.run(logger: logger, handler: handler)
3442
}.wait()
3543
}

0 commit comments

Comments
 (0)