Skip to content

Commit

Permalink
Preserve Types. (#2)
Browse files Browse the repository at this point in the history
* Preserve Types.
* Go inline!
  • Loading branch information
fabianfett committed Oct 19, 2022
1 parent 91b3a48 commit 7745274
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 50 deletions.
23 changes: 16 additions & 7 deletions Sources/AWSLambdaRuntimeCore/LambdaHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,12 @@ public protocol SimpleLambdaHandler {
func decode(buffer: ByteBuffer) throws -> Event
}

@usableFromInline
final class CodableSimpleLambdaHandler<Underlying: SimpleLambdaHandler>: ByteBufferLambdaHandler {
private let handler: Underlying
private var outputBuffer: ByteBuffer
@usableFromInline
let handler: Underlying
@usableFromInline
private(set) var outputBuffer: ByteBuffer

@inlinable
static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture<CodableSimpleLambdaHandler> {
Expand Down Expand Up @@ -181,9 +184,12 @@ public protocol LambdaHandler {
func decode(buffer: ByteBuffer) throws -> Event
}

@usableFromInline
final class CodableLambdaHandler<Underlying: LambdaHandler>: ByteBufferLambdaHandler {
private let handler: Underlying
private var outputBuffer: ByteBuffer
@usableFromInline
let handler: Underlying
@usableFromInline
private(set) var outputBuffer: ByteBuffer

@inlinable
static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture<CodableLambdaHandler> {
Expand Down Expand Up @@ -329,9 +335,12 @@ extension EventLoopLambdaHandler where Output == Void {
public func encode(value: Output, into buffer: inout ByteBuffer) throws {}
}

internal final class CodableEventLoopLambdaHandler<Underlying: EventLoopLambdaHandler>: ByteBufferLambdaHandler {
private let handler: Underlying
private var outputBuffer: ByteBuffer
@usableFromInline
final class CodableEventLoopLambdaHandler<Underlying: EventLoopLambdaHandler>: ByteBufferLambdaHandler {
@usableFromInline
let handler: Underlying
@usableFromInline
private(set) var outputBuffer: ByteBuffer

@inlinable
static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture<CodableEventLoopLambdaHandler> {
Expand Down
5 changes: 2 additions & 3 deletions Sources/AWSLambdaRuntimeCore/LambdaRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal final class LambdaRunner {
/// Run the user provided initializer. This *must* only be called once.
///
/// - Returns: An `EventLoopFuture<LambdaHandler>` fulfilled with the outcome of the initialization.
func initialize(handlerType: (some ByteBufferLambdaHandler).Type, logger: Logger, terminator: LambdaTerminator) -> EventLoopFuture<any ByteBufferLambdaHandler> {
func initialize<Handler: ByteBufferLambdaHandler>(handlerType: Handler.Type, logger: Logger, terminator: LambdaTerminator) -> EventLoopFuture<Handler> {
logger.debug("initializing lambda")
// 1. create the handler from the factory
// 2. report initialization error if one occurred
Expand All @@ -45,7 +45,6 @@ internal final class LambdaRunner {
)

return handlerType.makeHandler(context: context)
.map { $0 as any ByteBufferLambdaHandler }
// Hopping back to "our" EventLoop is important in case the factory returns a future
// that originated from a foreign EventLoop/EventLoopGroup.
// This can happen if the factory uses a library (let's say a database client) that manages its own threads/loops
Expand All @@ -60,7 +59,7 @@ internal final class LambdaRunner {
}
}

func run(handler: any ByteBufferLambdaHandler, logger: Logger) -> EventLoopFuture<Void> {
func run(handler: some ByteBufferLambdaHandler, logger: Logger) -> EventLoopFuture<Void> {
logger.debug("lambda invocation sequence starting")
// 1. request invocation from lambda runtime engine
self.isGettingNextInvocation = true
Expand Down
76 changes: 39 additions & 37 deletions Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,67 +19,34 @@ import NIOCore
/// `LambdaRuntime` manages the Lambda process lifecycle.
///
/// Use this API, if you build a higher level web framework which shall be able to run inside the Lambda environment.
public final class LambdaRuntime {
public final class LambdaRuntime<Handler: ByteBufferLambdaHandler> {
private let eventLoop: EventLoop
private let shutdownPromise: EventLoopPromise<Int>
private let logger: Logger
private let configuration: LambdaConfiguration

private let handlerType: any ByteBufferLambdaHandler.Type

private var state = State.idle {
willSet {
self.eventLoop.assertInEventLoop()
precondition(newValue.order > self.state.order, "invalid state \(newValue) after \(self.state.order)")
}
}

/// Create a new `LambdaRuntime`.
///
/// - parameters:
/// - handlerType: The ``SimpleLambdaHandler`` type the `LambdaRuntime` shall create and manage.
/// - eventLoop: An `EventLoop` to run the Lambda on.
/// - logger: A `Logger` to log the Lambda events.
public convenience init<Handler: SimpleLambdaHandler>(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) {
self.init(CodableSimpleLambdaHandler<Handler>.self, eventLoop: eventLoop, logger: logger)
}

/// Create a new `LambdaRuntime`.
///
/// - parameters:
/// - handlerType: The ``LambdaHandler`` type the `LambdaRuntime` shall create and manage.
/// - eventLoop: An `EventLoop` to run the Lambda on.
/// - logger: A `Logger` to log the Lambda events.
public convenience init<Handler: LambdaHandler>(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) {
self.init(CodableLambdaHandler<Handler>.self, eventLoop: eventLoop, logger: logger)
}

/// Create a new `LambdaRuntime`.
///
/// - parameters:
/// - handlerType: The ``EventLoopLambdaHandler`` type the `LambdaRuntime` shall create and manage.
/// - eventLoop: An `EventLoop` to run the Lambda on.
/// - logger: A `Logger` to log the Lambda events.
public convenience init<Handler: EventLoopLambdaHandler>(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) {
self.init(CodableEventLoopLambdaHandler<Handler>.self, eventLoop: eventLoop, logger: logger)
}

/// Create a new `LambdaRuntime`.
///
/// - parameters:
/// - handlerType: The ``ByteBufferLambdaHandler`` type the `LambdaRuntime` shall create and manage.
/// - eventLoop: An `EventLoop` to run the Lambda on.
/// - logger: A `Logger` to log the Lambda events.
public convenience init(_ handlerType: (some ByteBufferLambdaHandler).Type, eventLoop: EventLoop, logger: Logger) {
public convenience init(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) {
self.init(handlerType: handlerType, eventLoop: eventLoop, logger: logger, configuration: .init())
}

init(handlerType: (some ByteBufferLambdaHandler).Type, eventLoop: EventLoop, logger: Logger, configuration: LambdaConfiguration) {
init(handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger, configuration: LambdaConfiguration) {
self.eventLoop = eventLoop
self.shutdownPromise = eventLoop.makePromise(of: Int.self)
self.logger = logger
self.configuration = configuration
self.handlerType = handlerType
}

deinit {
Expand Down Expand Up @@ -118,7 +85,7 @@ public final class LambdaRuntime {
let terminator = LambdaTerminator()
let runner = LambdaRunner(eventLoop: self.eventLoop, configuration: self.configuration)

let startupFuture = runner.initialize(handlerType: self.handlerType, logger: logger, terminator: terminator)
let startupFuture = runner.initialize(handlerType: Handler.self, logger: logger, terminator: terminator)
startupFuture.flatMap { handler -> EventLoopFuture<Result<Int, Error>> in
// after the startup future has succeeded, we have a handler that we can use
// to `run` the lambda.
Expand Down Expand Up @@ -229,5 +196,40 @@ public final class LambdaRuntime {
}
}

public enum LambdaRuntimeFactory {
/// Create a new `LambdaRuntime`.
///
/// - parameters:
/// - handlerType: The ``SimpleLambdaHandler`` type the `LambdaRuntime` shall create and manage.
/// - eventLoop: An `EventLoop` to run the Lambda on.
/// - logger: A `Logger` to log the Lambda events.
@inlinable
public static func makeRuntime<H: SimpleLambdaHandler>(_ handlerType: H.Type, eventLoop: any EventLoop, logger: Logger) -> LambdaRuntime<some ByteBufferLambdaHandler> {
LambdaRuntime<CodableSimpleLambdaHandler<H>>(CodableSimpleLambdaHandler<H>.self, eventLoop: eventLoop, logger: logger)
}

/// Create a new `LambdaRuntime`.
///
/// - parameters:
/// - handlerType: The ``LambdaHandler`` type the `LambdaRuntime` shall create and manage.
/// - eventLoop: An `EventLoop` to run the Lambda on.
/// - logger: A `Logger` to log the Lambda events.
@inlinable
public static func makeRuntime<H: LambdaHandler>(_ handlerType: H.Type, eventLoop: any EventLoop, logger: Logger) -> LambdaRuntime<some ByteBufferLambdaHandler> {
LambdaRuntime<CodableLambdaHandler<H>>(CodableLambdaHandler<H>.self, eventLoop: eventLoop, logger: logger)
}

/// Create a new `LambdaRuntime`.
///
/// - parameters:
/// - handlerType: The ``EventLoopLambdaHandler`` type the `LambdaRuntime` shall create and manage.
/// - eventLoop: An `EventLoop` to run the Lambda on.
/// - logger: A `Logger` to log the Lambda events.
@inlinable
public static func makeRuntime<H: EventLoopLambdaHandler>(_ handlerType: H.Type, eventLoop: any EventLoop, logger: Logger) -> LambdaRuntime<some ByteBufferLambdaHandler> {
LambdaRuntime<CodableEventLoopLambdaHandler<H>>(CodableEventLoopLambdaHandler<H>.self, eventLoop: eventLoop, logger: logger)
}
}

/// This is safe since lambda runtime synchronizes by dispatching all methods to a single `EventLoop`
extension LambdaRuntime: @unchecked Sendable {}
6 changes: 3 additions & 3 deletions Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class LambdaRuntimeTest: XCTestCase {

let eventLoop = eventLoopGroup.next()
let logger = Logger(label: "TestLogger")
let runtime = LambdaRuntime(StartupErrorHandler.self, eventLoop: eventLoop, logger: logger)
let runtime = LambdaRuntimeFactory.makeRuntime(StartupErrorHandler.self, eventLoop: eventLoop, logger: logger)

// eventLoop.submit in this case returns an EventLoopFuture<EventLoopFuture<ByteBufferHandler>>
// which is why we need `wait().wait()`
Expand All @@ -51,7 +51,7 @@ class LambdaRuntimeTest: XCTestCase {

let eventLoop = eventLoopGroup.next()
let logger = Logger(label: "TestLogger")
let runtime = LambdaRuntime(EchoHandler.self, eventLoop: eventLoop, logger: logger)
let runtime = LambdaRuntimeFactory.makeRuntime(EchoHandler.self, eventLoop: eventLoop, logger: logger)

XCTAssertNoThrow(_ = try eventLoop.flatSubmit { runtime.start() }.wait())
XCTAssertThrowsError(_ = try runtime.shutdownFuture.wait()) {
Expand Down Expand Up @@ -98,7 +98,7 @@ class LambdaRuntimeTest: XCTestCase {

let eventLoop = eventLoopGroup.next()
let logger = Logger(label: "TestLogger")
let runtime = LambdaRuntime(ShutdownErrorHandler.self, eventLoop: eventLoop, logger: logger)
let runtime = LambdaRuntimeFactory.makeRuntime(ShutdownErrorHandler.self, eventLoop: eventLoop, logger: logger)

XCTAssertNoThrow(try eventLoop.flatSubmit { runtime.start() }.wait())
XCTAssertThrowsError(try runtime.shutdownFuture.wait()) { error in
Expand Down

0 comments on commit 7745274

Please sign in to comment.