Skip to content

Allow integration with API Frameworks: Public Lifecycle #58

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

Merged
merged 3 commits into from
Apr 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions Sources/AWSLambdaRuntime/Lambda.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,12 @@ public enum Lambda {
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.stop()
}
return lifecycle.start().always { _ in
lifecycle.shutdown()
signalSource.cancel()
}
return lifecycle.start().flatMap {
return lifecycle.shutdownFuture.always { _ in
signalSource.cancel()
}
}
}
}
Expand Down
82 changes: 54 additions & 28 deletions Sources/AWSLambdaRuntime/LambdaLifecycle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,30 @@ import NIO
import NIOConcurrencyHelpers

extension Lambda {
internal final class Lifecycle {
/// `Lifecycle` manages the Lambda process lifecycle.
public final class Lifecycle {
private let eventLoop: EventLoop
private let shutdownPromise: EventLoopPromise<Int>
private let logger: Logger
private let configuration: Configuration
private let factory: LambdaHandlerFactory

private var _state = State.idle
private let stateLock = Lock()

/// Create a new `Lifecycle`.
///
/// - parameters:
/// - eventLoop: An `EventLoop` to run the Lambda on.
/// - logger: A `Logger` to log the Lambda events.
/// - factory: A `LambdaHandlerFactory` to create the concrete Lambda handler.
public convenience init(eventLoop: EventLoop, logger: Logger, factory: @escaping LambdaHandlerFactory) {
self.init(eventLoop: eventLoop, logger: logger, configuration: .init(), factory: factory)
}

init(eventLoop: EventLoop, logger: Logger, configuration: Configuration, factory: @escaping LambdaHandlerFactory) {
self.eventLoop = eventLoop
self.shutdownPromise = eventLoop.makePromise(of: Int.self)
self.logger = logger
self.configuration = configuration
self.factory = factory
Expand All @@ -39,46 +52,46 @@ extension Lambda {
}
}

private var state: State {
get {
self.stateLock.withLock {
self._state
}
}
set {
self.stateLock.withLockVoid {
precondition(newValue.order > _state.order, "invalid state \(newValue) after \(self._state)")
self._state = newValue
}
}
/// The `Lifecycle` shutdown future.
///
/// - Returns: An `EventLoopFuture` that is fulfilled after the Lambda lifecycle has fully shutdown.
public var shutdownFuture: EventLoopFuture<Int> {
self.shutdownPromise.futureResult
}

func start() -> EventLoopFuture<Int> {
/// Start the `Lifecycle`.
///
/// - Returns: An `EventLoopFuture` that is fulfilled after the Lambda hander has been created and initiliazed, and a first run has been schduled.
public func start() -> EventLoopFuture<Void> {
logger.info("lambda lifecycle starting with \(self.configuration)")
self.state = .initializing
// triggered when the Lambda has finished its last run
let finishedPromise = self.eventLoop.makePromise(of: Int.self)
finishedPromise.futureResult.always { _ in
self.markShutdown()
}.cascade(to: self.shutdownPromise)
var logger = self.logger
logger[metadataKey: "lifecycleId"] = .string(self.configuration.lifecycle.id)
let runner = Runner(eventLoop: self.eventLoop, configuration: self.configuration)
return runner.initialize(logger: logger, factory: self.factory).flatMap { handler in
return runner.initialize(logger: logger, factory: self.factory).map { handler in
self.state = .active(runner, handler)
return self.run()
self.run(promise: finishedPromise)
}
}

func stop() {
self.logger.debug("lambda lifecycle stopping")
self.state = .stopping
// MARK: - Private

/// Begin the `Lifecycle` shutdown.
public func shutdown() {
self.state = .shuttingdown
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should wrap this in an

eventLoop.submit {
  self.state = .shuttingdown
}

to ensure needed thread safety.

Copy link
Contributor

@tomerd tomerd Apr 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

state is protected with lock, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You‘re right. Sorry.

}

func shutdown() {
self.logger.debug("lambda lifecycle shutdown")
private func markShutdown() {
self.state = .shutdown
}

@inline(__always)
private func run() -> EventLoopFuture<Int> {
let promise = self.eventLoop.makePromise(of: Int.self)

private func run(promise: EventLoopPromise<Int>) {
func _run(_ count: Int) {
switch self.state {
case .active(let runner, let handler):
Expand All @@ -96,23 +109,36 @@ extension Lambda {
promise.fail(error)
}
}
case .stopping, .shutdown:
case .shuttingdown:
promise.succeed(count)
default:
preconditionFailure("invalid run state: \(self.state)")
}
}

_run(0)
}

return promise.futureResult
private var state: State {
get {
self.stateLock.withLock {
self._state
}
}
set {
self.stateLock.withLockVoid {
precondition(newValue.order > self._state.order, "invalid state \(newValue) after \(self._state)")
self._state = newValue
}
self.logger.debug("lambda lifecycle state: \(newValue)")
}
}

private enum State {
case idle
case initializing
case active(Runner, ByteBufferLambdaHandler)
case stopping
case shuttingdown
case shutdown

internal var order: Int {
Expand All @@ -123,7 +149,7 @@ extension Lambda {
return 1
case .active:
return 2
case .stopping:
case .shuttingdown:
return 3
case .shutdown:
return 4
Expand Down