-
Notifications
You must be signed in to change notification settings - Fork 113
RFC: Added ServiceLifecycle dependency and started integration #141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
preconditionFailure("Failed to shutdown service: \(error)") | ||
} | ||
|
||
eventLoop.shutdownGracefully { error in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't that also be triggered by the lifecycle shutdown as a lifecycle task?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think thats a good idea: register the event loop in L110 then just call serviceLifecycle.shutdown
@@ -118,11 +121,19 @@ public enum Lambda { | |||
#if DEBUG | |||
signalSource.cancel() | |||
#endif | |||
eventLoop.shutdownGracefully { error in | |||
|
|||
serviceLifecycle.shutdown { (error) in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why (error)
?
@@ -32,13 +33,17 @@ extension Lambda { | |||
/// - note: The `EventLoop` is shared with the Lambda runtime engine and should be handled with extra care. | |||
/// Most importantly the `EventLoop` must never be blocked. | |||
public let eventLoop: EventLoop | |||
|
|||
/// `ServiceLifecycle` to register services with | |||
public let serviceLifecycle: ServiceLifecycle |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe simplify name to "lifecycle"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if just lifecycle
might add confusion because of the Lambda.Lifecycle
. Your call.
@@ -22,6 +23,7 @@ extension Lambda { | |||
/// - note: It is intended to be used within a single `EventLoop`. For this reason this class is not thread safe. | |||
public final class Lifecycle { | |||
private let eventLoop: EventLoop | |||
private let serviceLifecycle: ServiceLifecycle |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto on name
let promise = self.eventLoop.makePromise(of: ByteBufferLambdaHandler.self) | ||
// after we have created the LambdaHandler we must now start the services. | ||
// in order to not have to map once our success case returns the handler. | ||
serviceLifecycle.start { (error) in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should add sugar in the lifecycle NIO compact module to make this kind of integration easier. wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defer { | ||
serviceLifecycle.shutdown() | ||
serviceLifecycle.wait() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we can now simplify the shutdown logic a bit by relying more on the users using lifecycle
defer { | ||
serviceLifecycle.shutdown() | ||
serviceLifecycle.wait() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we can register the server with lifecycle but maybe will make the test harder to reason about
this is cool. added some ideas to discuss |
75ba97e
to
35b1a3f
Compare
Hi @tomerd, I've taken some time to think about the
Let's look at a more complex If we the credentials can't be obtained from the parameter store, we must fail the Lambda in startup and report an error. We do this by using Right now I can't see how we can achieve this using
import AsyncHTTPClient
import AWSLambdaEvents
import AWSLambdaRuntimeCore
import Dispatch
import Logging
import MySQLKit
import NIO
import SotoSSM
public class MyLambdaHandler: EventLoopLambdaHandler {
public typealias In = SNS.Event
public typealias Out = Void
let httpClient: HTTPClient
let database: EventLoopGroupConnectionPool<MySQLConnectionSource>
public static func create(_ context: Lambda.InitializationContext) -> EventLoopFuture<ByteBufferLambdaHandler> {
let eventLoop = context.eventLoop
let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoop))
// serviceLifecycle.registerShutdown(label: "httpClient") { (completion) in
// httpClient.shutdown(completion)
// }
let awsClient = AWSClient(httpClientProvider: .shared(httpClient))
// serviceLifecycle.registerShutdown(label: "awsClient") { (completion) in
// awsClient.shutdown(completion)
// }
let client = SSM(client: awsClient, region: .useast1)
return eventLoop.makeSucceededFuture(()).flatMapThrowing { (_) -> String in
let stageName = try Lambda.env(for: "STAGE")
return stageName
}.flatMap { (stageName) -> EventLoopFuture<(SSM.GetParametersResult, String)> in
let parameters = [
"/\(stageName)/database/username",
"/\(stageName)/database/password",
]
return client.getParameters(.init(names: parameters)).map { ($0, stageName) }
}.flatMapThrowing { parameters, stageName in
let config = MySQLConfiguration(
hostname: try Lambda.env(for: "DATABASE_HOST"),
username: try parameters.value(for: "/\(stageName)/database/username"),
password: try parameters.value(for: "/\(stageName)/database/password"),
database: try Lambda.env(for: "DATABASE_NAME"),
tlsConfiguration: nil
)
let pool = EventLoopGroupConnectionPool(
source: MySQLConnectionSource(configuration: config),
on: eventLoop
)
// serviceLifecycle.registerShutdown(label: "database-pool") { (completion) in
// pool.shutdown(completion)
// }
return pool
}.flatMapThrowing { (pool) -> ByteBufferLambdaHandler in
MyLambdaHandler(database: pool, httpClient: httpClient)
}
}
init(database: EventLoopGroupConnectionPool<MySQLConnectionSource>, httpClient: HTTPClient) {
self.database = database
self.httpClient = httpClient
}
public func handle(context: Lambda.Context, event: SNS.Event) -> EventLoopFuture<Void> {
// ... do something using the httpClient and the database
preconditionFailure()
}
}
extension SSM.GetParametersResult {
func value(for name: String) throws -> String {
guard let value = parameters?.first(where: { $0.name == name })?.value else {
throw ReviewImporterLambdaHandler.Error.missingParameterStoreValue(name: name, invalidParameters: invalidParameters)
}
return value
}
}
extension Lambda {
static func env(for name: String) throws -> String {
guard let env = Lambda.env(name) else {
throw ReviewImporterLambdaHandler.Error.missingEnvironmentVariable(name)
}
return env
}
} |
hi @fabianfett if you want to keep this around, could you please update this PR to be against the |
35b1a3f
to
9f976d7
Compare
The current start and shutdown services is okay but not great. Thankfully
ServiceLifecycle
has been released two days ago. Let's see if we can use it for AWS Lambda Runtime.This is a draft pr, exploring possibilities and starting a discussion.
Open ends/questions
ServiceLifecycle
to the user but don't use it ourselves, even though we could. But for this we will need to think about state handling (current state of mind):1.0
?