Swift AWS Lambda Runtime Supports Lambda Managed Instances

AWS recently announced Lambda Managed Instances, a new deployment option that runs Lambda functions on customer-owned EC2 instances while AWS manages the operational aspects. This brings an important change to the Lambda programming model.

What's Different: Multi-Concurrent Invocations

Unlike traditional Lambda functions where one execution environment handles one invocation at a time, Lambda Managed Instances support multi-concurrent invocations. This means one execution environment can handle multiple invocations simultaneously, each processed by a different runtime worker. As AWS describes it:

Multiple invocations can execute simultaneously within the same execution environment, each handled by a different runtime worker.

The execution environment remains continuously active, processing invocations as they arrive without freezing between invocations. This yields better utilization of underlying EC2 instances.

Lambda Managed Instances are best suited for:

  • High volume-predictable workloads - Steady-state workloads without unexpected traffic spikes

  • Performance-critical applications - Access to latest CPUs, varying memory-CPU ratios, and high network throughput

  • Regulatory requirements - Granular governance with control over VPC and instance placement

  • Variety of applications - Event-driven applications, media/data processing, web applications, and legacy workloads migrating to serverless

Swift Runtime: Built for Concurrency from Day One

Good news: the Swift AWS Lambda Runtime was designed with concurrency support from the beginning. However, in it's current form, the Swift runtime handles invocations in sequence.

We've tested it with Lambda Managed Instances and confirmed it works as expected, handling multiple invocations correctly without cross-contamination between requests.

In the future, we might consider adding support for concurrent invocations (handling multiple requests in parallel), depending on user feedback and demand. There are multiple strategies to implement this: launch multiple Swift processes, each polling for events and handling them in sequence in a process, or create a Task upon reception of events and run your handler in the Task.

Important Considerations for Your Code

While the runtime handles concurrency safely, you need to ensure your function code is concurrency-safe:

1. Respect Swift Concurrency Safety

If you're using workarounds to bypass Swift's concurrency safety checks (such as @unchecked Sendable or unsafe transfer mechanisms), your code may break in a multi-concurrent environment. The compiler's concurrency safety features exist for good reasons—trust them.

2. Avoid Shared State Between Invocations

Each invocation must be isolated. Pay special attention to:

  • File operations: Use unique file names for each invocation. Include the request ID in file names to prevent conflicts:

let fileName = "/tmp/\(context.requestID)-data.json"

  • Global variables: Avoid mutable global state that could be accessed by multiple concurrent invocations

  • Shared resources: Database connections, file handles, and other resources must be managed with concurrency in mind

Getting Started

The Swift AWS Lambda Runtime works seamlessly with Lambda Managed Instances—no code changes required if your function is already concurrency-safe. Simply configure your Lambda function with a capacity provider and deploy.

For more details on Lambda Managed Instances and the multi-concurrent execution model, see the AWS documentation.

7 Likes

What's the isolation model? Is this running a single process with different tasks handling concurrent requests? Multiple processes with a shared file system? Multiple containers with sandboxed file system?

1 Like

Lambda starts one runtime per EC2 instance. The Runtime can create multiple "worker"
This is the AWS wording from Understanding the Lambda Managed Instances execution environment - AWS Lambda

For the Swift runtime, it translates to one process is started and polls events. It process events as they are received.

So it's one Swift process per EC2 instance, and that one process handles concurrent Lambda invocations?

// Edit: when you say “it”, does it mean one Swift process is one worker, or one “runtime”?

Apologies, my original post was ambiguous. I edited it for clarification.

With current version of the Swift runtime: there is one Swift process per EC2 instance and that process handles events in sequence. The big difference from traditional Lambda is that the runtime keeps running for a long period of time (as long as the EC2 instance is up or until the runtime crashes :-) ) It doesn't freeze in between invocations and is never terminated.

At the moment, there is no concurrent invocation of the user handler() in the Swift process. That would require an API change (the handler and the writers must conform to Sendable)

Question for the community:

What would be the best strategy to implement concurrent event handling on a single EC2 instance ?

  1. The Python runtime choose to spawn multiple processes.

  2. The NodeJS runtime choose to spawn threads in the same process. Each thread contains the full runtime interface client (each thread has its own HTTP GET /next event loop)

  3. Keep one HTTP client loop and run the user handler in a separate Task (one Task per event). That would required to make the handler() and the writers conform to Sendable.

The first approach is the simplest and doesn't require change in the API.
The second option has minimal API changes. The Swift process will start multiple "workers" (Task) made of the current implementation. The number of workers might be configurable with an environment variable.

The third approach will require a change in the API to add conformance to Sendable

I’d probably vote for (1), multiple processes.

One reason why (3) could be interesting is if there are expensive caches you want to keep warm inside a single instance and multiple parallel workers. But it also increases the blast radius (data leaks, crashes affecting more unrelated requests.) So not sure it’s worth it.

1 Like

Here is a PR to see what it would like with 2/

This creates multiple Runtime HTTP Clients (Lambda calls these Runtime Interface Client or RIC) as per the AWS_LAMBDA_MAX_CONCURRENCY env variable.

Each RIC runs in its own Task. When AWS_LAMBDA_MAX_CONCURRENCY is absent or ==1, no Task are created and existing behavior is preserved.

The downside is that the LambdaHandler protocol and all the types it touches must be marked Sendable, which I know @fabianfett worked hard to avoid so far.

If we choose this, this would require a major version bump.

I'll try a PR for 1/ too but I have to figure out a DX friendly way to do it.
The process entry point is under the control of the user, not the library. I'll need more thinking.

Now that I made the hard work for making LambdaHandler conform to Sendable, implementing 3 should not be too hard. It's just the matter of where to start the Taskgroup. With the existing PR for 2/, it is started in the LambdaRuntime class. With 3/, it would be in the Lambda.runLoop() function

Based on other community feedback, we decided to not make change to the current API and to implement a separate LambdaRuntime struct that will enforce your handler function to be Sendable.

The new LambdaManagedRuntime has the same signature API as the existing LambdaRuntime but will enforce its handler to conform to Sendable. That runtime starts multiple HTTP Client loop to fetch events from Lambda and dispatch them to your handler function in parallel (using TaskGroups). You control in the console (or the function configuration) the maximum number of concurrent handler execution per EC2 instance.

This is work in progress. We need code reviewers.

Here is the PR with the core changes, i.e. the introduction of a LambdaManagedInstance struct.

And this is a second PR that adds convenience structs, such as the Adapters and ClosureHandler. These are mostly copy / paste of existing struct, with the addition of the conformance to Sendable.

This later PR also includes an extensive example to show the (minimal) code change required for those of you willing to deploy your functions on Lambda Managed Instances.

A LambdaManagedRuntime can be deployed on a standard Lambda runtime environment. The opposite is not true, unless you paid attention to concurrency. This is what the conformance for Sendable will do for you automatically.