You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implements the minimum viable slice of the Amazon.Lambda.DurableExecution
SDK: a workflow can run StepAsync and WaitAsync against a real Lambda,
with replay-aware checkpointing wired through to the AWS service.
Public API surface introduced:
- DurableFunction.WrapAsync — entry point that handles the durable
execution envelope (input hydration, output construction, status mapping)
- IDurableContext.StepAsync / WaitAsync (4 Step overloads, 1 Wait)
- StepConfig with serializer hook (retry deferred to follow-up PR)
- ICheckpointSerializer interface
- [DurableExecution] attribute (recognized by future source generator)
- DurableExecutionException base + StepException
Internals:
- DurableExecutionHandler — Task.WhenAny race between user code and
the suspension signal, returning Succeeded/Failed/Pending
- ExecutionState — replay-aware operation lookup and pending checkpoint
buffer
- OperationIdGenerator — deterministic, replay-stable IDs
- TerminationManager — TaskCompletionSource-based suspension trigger
- LambdaDurableServiceClient — wraps AWSSDK.Lambda's checkpoint and
state APIs
Tests:
- 86 unit tests covering enums, exceptions, models, configs,
ID generation, termination, execution state, the handler race,
the context (Step + Wait paths), and the WrapAsync entry point
- 8 end-to-end integration tests deploying real Lambdas via Docker on
the provided.al2023 runtime: StepWaitStep, MultipleSteps, WaitOnly,
LongerWait, ReplayDeterminism, RetrySucceeds, RetryExhausts, StepFails
Out of scope (follow-up PRs):
- IRetryStrategy, ExponentialRetryStrategy, retry decision factories
- DefaultJsonCheckpointSerializer
- DurableLogger replay-suppression (currently returns NullLogger)
- Callbacks, InvokeAsync, ParallelAsync, MapAsync, RunInChildContextAsync,
WaitForConditionAsync — interface intentionally does not declare them
- Annotations source-generator integration
- DurableTestRunner / Amazon.Lambda.DurableExecution.Testing package
- dotnet new lambda.DurableFunction blueprint
stack-info: PR: #2360, branch: GarrettBeatty/stack/2
remove
You'd also need to manually configure the CloudFormation template with `DurableConfig` and managed policies:
254
291
255
292
```json
@@ -877,14 +914,15 @@ When `RunAsync` starts, it kicks off two tasks in parallel: user code and a term
877
914
878
915
The `TerminationManager` is a thin wrapper around `TaskCompletionSource<TerminationResult>`:
879
916
-`TerminationTask` -- a Task that hangs forever until `Terminate()` is called
880
-
-`Terminate(reason)` -- resolves the TCS, causing the race to pick termination
917
+
-`Terminate(reason)` -- resolves the TCS and cancels `CancellationToken`, causing the race to pick termination
918
+
-`CancellationToken` -- cancelled when `Terminate()` fires; exposed via `IDurableContext.CancellationToken` so user code can cooperatively stop after suspension
@@ -968,6 +1080,24 @@ public interface IDurableContext
968
1080
StepConfig? config=null,
969
1081
CancellationTokencancellationToken=default);
970
1082
1083
+
/// <summary>
1084
+
/// Execute a step (simplified overload without IStepContext).
1085
+
/// </summary>
1086
+
Task<T> StepAsync<T>(
1087
+
Func<Task<T>> func,
1088
+
string? name=null,
1089
+
StepConfig? config=null,
1090
+
CancellationTokencancellationToken=default);
1091
+
1092
+
/// <summary>
1093
+
/// Execute a step that returns no value (simplified overload).
1094
+
/// </summary>
1095
+
TaskStepAsync(
1096
+
Func<Task> func,
1097
+
string? name=null,
1098
+
StepConfig? config=null,
1099
+
CancellationTokencancellationToken=default);
1100
+
971
1101
/// <summary>
972
1102
/// Suspend execution for the specified duration.
973
1103
/// Throws ArgumentOutOfRangeException if duration is less than 1 second.
@@ -1087,7 +1217,11 @@ public record DurableBranch<T>(string Name, Func<IDurableContext, Task<T>> Func)
1087
1217
1088
1218
#### CancellationToken behavior
1089
1219
1090
-
All methods accept a `CancellationToken` that follows standard .NET semantics: cancellation throws `OperationCanceledException` and the execution fails. Cancellation does **not** trigger suspension — those are separate concepts. The durable execution service handles timeout scenarios automatically: if Lambda terminates mid-execution, the next invocation simply replays from the last checkpoint. For advanced users who want to suspend gracefully before timeout, check `context.LambdaContext.RemainingTime` and return early.
1220
+
All methods accept a `CancellationToken` that follows standard .NET semantics: cancellation throws `OperationCanceledException` and the execution fails. Cancellation does **not** trigger suspension — those are separate concepts.
1221
+
1222
+
`IDurableContext.CancellationToken` is a context-level token that is automatically cancelled when the execution suspends (wait, retry, or callback). This allows in-flight user code to cooperatively stop after suspension rather than continuing to consume resources on the Lambda container until freeze. User code that performs long-running work (HTTP calls, file I/O) should pass this token to those operations.
1223
+
1224
+
The durable execution service handles timeout scenarios automatically: if Lambda terminates mid-execution, the next invocation simply replays from the last checkpoint. For advanced users who want to suspend gracefully before timeout, check `context.LambdaContext.RemainingTime` and return early.
1091
1225
1092
1226
### Configuration Types
1093
1227
@@ -1579,13 +1713,29 @@ Both approaches produce a self-contained executable that the Lambda custom runti
1579
1713
1580
1714
### NativeAOT compatibility
1581
1715
1582
-
The SDK is AOT-friendly but does not require AOT. The default JSON serialization uses reflection (standard `System.Text.Json` behavior), which works in JIT mode. For NativeAOT deployments, provide a `JsonSerializerContext` via the `ICheckpointSerializer<T>` interface — this avoids all runtime reflection and is fully trim-safe. The SDK itself avoids `Activator.CreateInstance`, `Type.GetType()`, and other reflection patterns, and uses `[DynamicallyAccessedMembers]` trimming annotations where needed.
1716
+
The SDK is AOT-friendly but does not require AOT. The default JSON serialization uses reflection (standard `System.Text.Json` behavior), which works in JIT mode. For NativeAOT deployments, AOT safety is addressed at two levels:
1717
+
1718
+
1.**Entry point (`DurableFunction.WrapAsync`)** — pass a `JsonSerializerContext` that includes type info for your `TInput` and `TOutput` types. The reflection-based overloads are annotated with `[RequiresUnreferencedCode]` / `[RequiresDynamicCode]` so the trimmer will warn if you use them in AOT builds.
1719
+
1720
+
2.**Step checkpoints (`StepConfig.Serializer`)** — for custom or complex types checkpointed by `StepAsync`, provide an `ICheckpointSerializer` via `StepConfig` (e.g., `JsonCheckpointSerializer<T>` backed by a source-generated `JsonTypeInfo<T>`).
1721
+
1722
+
The SDK itself avoids `Activator.CreateInstance`, `Type.GetType()`, and other reflection patterns, and uses `[DynamicallyAccessedMembers]` trimming annotations where needed.
When no `LambdaClientFactory` is specified, the generated code creates a default `AmazonLambdaClient`. For the manual handler path, pass the client directly to `DurableExecutionHandler.RunAsync`.
1854
+
When no `LambdaClientFactory` is specified, the generated code creates a default `AmazonLambdaClient`. For the manual handler path (`DurableFunction.WrapAsync`), pass the client directly via the `IAmazonLambda lambdaClient` overload.
1705
1855
1706
1856
> **Dependency boundaries:**`Amazon.Lambda.Annotations` has **no dependency** on the AWS SDK or on `Amazon.Lambda.DurableExecution`. The Annotations source generator references durable execution types by fully-qualified name strings only — it never takes a compile-time dependency on the durable package. The `[DurableExecution]` attribute is defined in `Amazon.Lambda.DurableExecution`, and the generated code resolves against the user's project references. There is only one source generator (Annotations) — no coordination between multiple generators is needed.
0 commit comments