Skip to content

Durable Execution SDK for .NET β€” Preview Now Available πŸŽ‰Β #2418

Description

@GarrettBeatty

Durable Execution SDK for .NET β€” Preview Now Available πŸŽ‰

The AWS Lambda .NET team is excited to announce the preview of the AWS Lambda Durable Execution SDK for .NET (Amazon.Lambda.DurableExecution).

Durable Execution lets you build resilient, long-running Lambda functions that automatically checkpoint their progress and resume after failures. Workflows can run for up to one year, and you're only charged for active compute time β€” not for the time spent waiting.

Why Durable Execution?

Today, orchestrating multi-step workflows in Lambda often means stitching together Step Functions state machines, SQS queues, or DynamoDB-backed state, and writing your own logic to track progress and recover from failures. Durable Execution lets you express that same workflow as ordinary C# code β€” await your steps, your waits, and your callbacks β€” while the SDK handles checkpointing, retries, and replay-safe resumption for you.

Key features

  • Automatic checkpointing β€” progress is saved after each step; failures resume from the last checkpoint.
  • Cost-effective waits β€” suspend execution for minutes, hours, or days without compute charges.
  • Configurable retries β€” built-in retry strategies with exponential backoff and jitter.
  • Replay safety β€” functions deterministically resume from checkpoints after interruptions.
  • Type safety β€” full generic type support for step results.
  • AOT-friendly β€” pluggable ILambdaSerializer for trimmed / Native AOT functions.

For example, a multi-step order workflow with a built-in wait becomes:

private async Task<OrderResult> Workflow(Order order, IDurableContext ctx)
{
    var reservation = await ctx.StepAsync(
        async (_, ct) => await InventoryService.ReserveAsync(order.Items, ct),
        name: "reserve-inventory");

    var payment = await ctx.StepAsync(
        async (_, ct) => await PaymentService.ChargeAsync(order.PaymentMethod, order.Total, ct),
        name: "process-payment");

    // Suspend for 2 hours with no compute charges, then resume.
    await ctx.WaitAsync(TimeSpan.FromHours(2), name: "warehouse-processing");

    var shipment = await ctx.StepAsync(
        async (_, ct) => await ShippingService.ShipAsync(reservation, order.Address, ct),
        name: "confirm-shipment");

    return new OrderResult(order.Id, shipment.TrackingNumber);
}

The first preview is available on NuGet:

πŸ“¦ Amazon.Lambda.DurableExecution 0.1.1-preview

dotnet add package Amazon.Lambda.DurableExecution --version 0.1.1-preview

Preview status. Amazon.Lambda.DurableExecution is in active development (0.x). Public APIs may change before 1.0.

Deployment. Durable functions deploy as a plain zip package on the managed dotnet10 runtime β€” Both the executable programming model (a Main that builds a LambdaBootstrap) and the class-library programming model (a plain Handler method, the same model used by non-durable functions) are supported.

Deploying a durable function (zip on the managed runtime)

The end-to-end integration tests build, deploy, and invoke durable functions exactly this way. See DurableFunctionDeployment.cs for the full lifecycle (IAM role β†’ dotnet publish β†’ zip β†’ CreateFunction β†’ invoke), and the TestFunctions/ folder for runnable example functions (each paired with a test).

1. Project file β€” choose either programming model.

Executable model β€” build an executable named bootstrap:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <OutputType>Exe</OutputType>
    <AssemblyName>bootstrap</AssemblyName>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.DurableExecution" Version="0.1.1-preview" />
    <PackageReference Include="Amazon.Lambda.RuntimeSupport" Version="*" />
    <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="*" />
  </ItemGroup>
</Project>

Class-library model β€” a normal Lambda class library (no OutputType, no RuntimeSupport reference); the managed runtime supplies the bootstrap loop:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
    <AWSProjectType>Lambda</AWSProjectType>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.DurableExecution" Version="0.1.1-preview" />
    <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="*" />
  </ItemGroup>
</Project>

2. Handler β€” the executable model wires up LambdaBootstrap in Main; the class-library model declares the serializer with an assembly attribute and exposes a plain Handler.

Executable model:

public class OrderProcessor
{
    public static async Task Main()
    {
        var handler = new OrderProcessor();
        var serializer = new DefaultLambdaJsonSerializer();
        using var wrapper = HandlerWrapper.GetHandlerWrapper<DurableExecutionInvocationInput, DurableExecutionInvocationOutput>(
            handler.Handler, serializer);
        using var bootstrap = new LambdaBootstrap(wrapper);
        await bootstrap.RunAsync();
    }

    public Task<DurableExecutionInvocationOutput> Handler(
        DurableExecutionInvocationInput input, ILambdaContext context)
        => DurableFunction.WrapAsync<Order, OrderResult>(Workflow, input, context);

    private async Task<OrderResult> Workflow(Order order, IDurableContext ctx) { /* ... */ }
}

Class-library model:

[assembly: LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]

namespace OrderProcessor;

public class Function
{
    public Task<DurableExecutionInvocationOutput> Handler(
        DurableExecutionInvocationInput input, ILambdaContext context)
        => DurableFunction.WrapAsync<Order, OrderResult>(Workflow, input, context);

    private async Task<OrderResult> Workflow(Order order, IDurableContext ctx) { /* ... */ }
}

3. Publish and zip the package:

# Publish a framework-dependent Linux build (the dotnet:10 managed runtime supplies the runtime)
dotnet publish -c Release -r linux-x64 --self-contained false -o bin/publish

# Zip the publish output (no top-level folder)
cd bin/publish && zip -r ../function.zip . && cd -

4. Create the function as a zip package on the managed dotnet10 runtime, with a DurableConfig. The execution role needs both the basic execution policy and the durable execution policy:

  • arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
  • arn:aws:iam::aws:policy/service-role/AWSLambdaBasicDurableExecutionRolePolicy
await lambdaClient.CreateFunctionAsync(new CreateFunctionRequest
{
    FunctionName = "my-durable-fn",
    Runtime      = "dotnet10",
    // Executable model: "bootstrap".
    // Class-library model: an Assembly::Namespace.Type::Method string,
    //   e.g. "OrderProcessor::OrderProcessor.Function::Handler".
    Handler      = "bootstrap",
    Role         = roleArn,
    Code         = new FunctionCode { ZipFile = new MemoryStream(File.ReadAllBytes("bin/function.zip")) },
    Timeout      = 30,
    MemorySize   = 256,
    DurableConfig = new DurableConfig { ExecutionTimeout = 60 }
});

5. Invoke it with a DurableExecutionName, then track it via GetDurableExecution / GetDurableExecutionHistory:

await lambdaClient.InvokeAsync(new InvokeRequest
{
    FunctionName          = "my-durable-fn",
    Qualifier             = "$LATEST",
    Payload               = payload,
    DurableExecutionName  = $"order-{Guid.NewGuid():N}"
});

Core operations

Your handler delegates to DurableFunction.WrapAsync, which gives your workflow an IDurableContext β€” your interface to durable operations:

  • Steps β€” execute code with automatic checkpointing, retry strategies, and at-least/at-most-once semantics.
  • Wait β€” pause execution without compute charges.
  • Wait For Condition β€” poll until a condition is met, suspending between polls.
  • Callbacks β€” wait for external systems (approvals, webhooks) to respond.
  • Child Contexts β€” group related operations into isolated, checkpointed units.

πŸ“– Learn more in the Durable Execution SDK README, which includes a complete getting-started walkthrough and full examples of both the executable and class-library programming models.

Related SDKs

Durable Execution is available across multiple languages:


What's still to come 🚧

This is an early preview and we're actively building. On the roadmap:

  • ParallelAsync β€” fan out and run multiple durable branches concurrently.
  • MapAsync β€” apply a durable operation across a collection of items.
  • Managed runtime support β€” run executable or class-library handlers on the managed dotnet10 runtime (no custom container image required).
  • Annotations source generator support β€” author durable functions with [DurableExecution]-style attributes.
  • Local testing SDK β€” run and step through durable workflows locally.
  • Large payload support β€” handle step inputs/outputs beyond the inline checkpoint size limits.
  • Project blueprint β€” a dotnet new template to scaffold a durable function.

We want your feedback πŸ™

We cannot overstate how critical your feedback is during preview. Please try it out and let us know what works, what doesn't, and what you'd like to see before 1.0 β€” comment on this issue!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions