-
Notifications
You must be signed in to change notification settings - Fork 118
Add support for Lambda Managed Instances without changing the public API [Convenience + Example] #623
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
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.
Pull request overview
This pull request adds support for Lambda Managed Instances, enabling Swift Lambda functions to handle multiple concurrent invocations within a single execution environment. The implementation introduces thread-safe runtime components with Sendable conformance while maintaining backward compatibility with the existing single-threaded Lambda runtime.
Key Changes:
- Introduced
LambdaManagedRuntimeclass with thread-safe handler execution supporting concurrent invocations based on theAWS_LAMBDA_MAX_CONCURRENCYenvironment variable - Created Sendable-conforming adapter types (
LambdaHandlerAdapterSendable,LambdaCodableAdapterSendable,LambdaJSONEventDecoderSendable,LambdaJSONOutputEncoderSendable) to ensure safe concurrent execution - Refactored
LambdaRuntimeto extract reusable methods (startRuntimeInterfaceClient,startLocalServer) shared withLambdaManagedRuntime
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
scripts/extract_aws_credentials.sh |
Removed script (likely no longer needed) |
Tests/AWSLambdaRuntimeTests/Utils.swift |
Added test helper for creating LambdaContext instances |
Tests/AWSLambdaRuntimeTests/LambdaManagedRuntimeTests.swift |
Comprehensive tests for concurrent handler execution, Sendable constraints, and thread-safe adapters |
Sources/AWSLambdaRuntime/Runtime/LambdaRuntime.swift |
Refactored to extract reusable methods and renamed atomic guard variable for clarity |
Sources/AWSLambdaRuntime/Runtime/LambdaRuntime+Codable.swift |
Added Sendable conformance to VoidEncoder |
Sources/AWSLambdaRuntime/ManagedRuntime/LambdaManagedRuntimeHandlers.swift |
Introduced ClosureHandlerSendable for thread-safe closure-based handlers |
Sources/AWSLambdaRuntime/ManagedRuntime/LambdaManagedRuntime.swift |
Core implementation of managed runtime with concurrency detection and multiple RIC support |
Sources/AWSLambdaRuntime/ManagedRuntime/LambdaManagedRuntime+ServiceLifecycle.swift |
ServiceLifecycle integration for managed runtime |
Sources/AWSLambdaRuntime/ManagedRuntime/LambdaManagedRuntime+Codable.swift |
Thread-safe adapter implementations for managed runtime |
Sources/AWSLambdaRuntime/FoundationSupport/LambdaRuntime+JSON.swift |
Convenience initializers for LambdaRuntime with JSON encoding/decoding |
Sources/AWSLambdaRuntime/FoundationSupport/LambdaManagedRuntime+JSON.swift |
Sendable JSON encoder/decoder implementations and convenience initializers for managed runtime |
Sources/AWSLambdaRuntime/FoundationSupport/LambdaHandler+JSON.swift |
Extracted common JSON handler support to separate file |
[email protected] |
Added ManagedRuntimeSupport trait and updated dependency versions |
Package.swift |
Added ManagedRuntimeSupport trait to default enabled traits and updated dependency versions |
Examples/Streaming+Codable/Tests/LambdaStreamingCodableTests.swift |
Updated logger label for consistency |
Examples/ManagedInstances/template.yaml |
SAM template for deploying managed instances examples |
Examples/ManagedInstances/Sources/Streaming/main.swift |
Streaming response example for managed instances |
Examples/ManagedInstances/Sources/HelloJSON/main.swift |
Simple JSON request/response example for managed instances |
Examples/ManagedInstances/Sources/BackgroundTasks/main.swift |
Background processing example for managed instances |
Examples/ManagedInstances/README.md |
Documentation for deploying and testing managed instances examples |
Examples/ManagedInstances/Package.swift |
Package definition for managed instances examples |
Examples/ManagedInstances/.gitignore |
Ignore patterns for managed instances examples |
.github/workflows/pull_request.yml |
Added ManagedInstances to CI examples list |
Comments suppressed due to low confidence (3)
Sources/AWSLambdaRuntime/FoundationSupport/LambdaManagedRuntime+JSON.swift:149
- Inconsistent default logger label. This initializer uses "LambdaRuntime" as the default logger label, but the previous initializer on line 122 uses "LambdaManagedRuntime". For consistency, this should likely also use "LambdaManagedRuntime" since it's an extension on LambdaManagedRuntime.
Sources/AWSLambdaRuntime/FoundationSupport/LambdaManagedRuntime+JSON.swift:207 - Inconsistent default logger label. This initializer uses "LambdaRuntime" as the default logger label, but line 122 uses "LambdaManagedRuntime". For consistency across the LambdaManagedRuntime extension, this should likely also use "LambdaManagedRuntime".
Sources/AWSLambdaRuntime/FoundationSupport/LambdaManagedRuntime+JSON.swift:176 - Inconsistent default logger label. This initializer uses "LambdaRuntime" as the default logger label, but line 122 uses "LambdaManagedRuntime". For consistency across the LambdaManagedRuntime extension, this should likely also use "LambdaManagedRuntime".
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Sources/AWSLambdaRuntime/ManagedRuntime/LambdaManagedRuntime.swift
Outdated
Show resolved
Hide resolved
- Remove deleted Lambda+JSON.swift file - Split JSON support into separate files: - LambdaRuntime+JSON.swift for regular runtime - LambdaManagedRuntime+JSON.swift for managed runtime - Clean up ManagedInstances example directory
… error (Bug #635) (#636) On fast machines, the local Lambda server crashes with: ``` Fatal error: Deinited NIOAsyncWriter without calling finish() ``` This occurs in `NIOAsyncChannelHandler.channelActive()` when child connection channels are created. ## Root Cause This is a known issue with NIO's async server channel API (see [swift-nio#2637](apple/swift-nio#2637)). **The fundamental problem:** 1. The async `bind()` API creates `NIOAsyncChannel` instances for incoming connections 2. These channels are yielded through an async stream to the server loop 3. When the serving task is cancelled (or completes), the async stream iteration stops 4. Any channels that were accepted but not yet read from the stream are dropped 5. These unread channels never have `executeThenClose()` called on them 6. Their `NIOAsyncWriter` is deallocated without `finish()` being called → fatal error **Why graceful shutdown doesn't help:** Even closing the server channel gracefully doesn't eliminate the race - there's a timing window where: - A connection is accepted and queued in the async stream - The server task is cancelled or completes - The queued channel is never read and gets dropped IMHO, this is an inherent limitation of the `async bind()` API when combined with task cancellation. ## Solution I stopped using the `async bind()` API entirely. Instead, I use the traditional callback-based `childChannelInitializer`: 1. Create `NIOAsyncChannel` directly in `childChannelInitializer` (synchronous context) 2. Immediately spawn a `Task.detached` to handle the connection 3. Each connection is handled independently, not through a cancellable async stream 4. Detached tasks are not affected by task group cancellation 5. Every channel has `executeThenClose()` called immediately, preventing the writer from being dropped This approach avoids the async stream entirely, eliminating the race condition. ## Changes - Replaced `async bind()` with traditional `childChannelInitializer` - Each connection spawns a `Task.detached` that immediately calls `executeThenClose()` - Removed the connection iteration loop (no longer needed) - Server task now simply waits for the channel to close - Simplified shutdown logic since there's no async stream to drain ## Trade-offs - Uses `Task.detached` (unstructured concurrency) to bridge NIO's event-loop world with Swift concurrency - This is necessary until NIO provides a new bootstrap API that properly handles cancellation - Each connection is handled independently rather than through structured concurrency ## Testing Tested on fast machines where the race condition was reliably reproducible. The crash no longer occurs. ## References - [swift-nio#2637](apple/swift-nio#2637) - Known issue with async server channels and cancellation - [Comment from NIO maintainer](apple/swift-nio#2637 (comment)) - Recommends avoiding cancellation or using callback-based API Fixes #635 --------- Co-authored-by: Sebastien Stormacq <[email protected]>
….com/awslabs/swift-aws-lambda-runtime into sebsto/lambda-managed-instances-core
…bs/swift-aws-lambda-runtime into sebsto/lambda-managed-instances-v2
This PR builds on #629 to add convenience structs (Handlers and Adapters) that are
SendableChanges
Added Sendable adapter types: Implemented
ClosureHandlerSendable- a thread-safe version of existing closure handler that enforcesSendableconformance for concurrent execution environments - and added conditional conformance toSendablefor other Adapters when the Handler isSendableEnhanced handler protocols for concurrency: Extended handler protocols to support
Sendableconstraints and concurrent response writing throughLambdaResponseStreamWriter & Sendable, enabling safe multi-threaded invocation processingCreated comprehensive Lambda Managed Instances examples: Built three demonstration functions showcasing concurrent execution capabilities, streaming responses, and background processing patterns specific to the new managed instances deployment model
Context
Lambda Managed Instances support multi-concurrent invocations where multiple invocations execute simultaneously within the same execution environment. The runtime now detects the configured concurrency level and launches the appropriate number of RICs to handle concurrent requests efficiently.
When
AWS_LAMBDA_MAX_CONCURRENCYis 1 or unset, the runtime maintains the existing single-threaded behaviour for optimal performance on traditional Lambda deployments.