Skip to content

Add RunInChildContextAsync#2370

Open
GarrettBeatty wants to merge 2 commits into
GarrettBeatty/stack/3from
gcbeatty/durable-child-context
Open

Add RunInChildContextAsync#2370
GarrettBeatty wants to merge 2 commits into
GarrettBeatty/stack/3from
gcbeatty/durable-child-context

Conversation

@GarrettBeatty
Copy link
Copy Markdown
Contributor

@GarrettBeatty GarrettBeatty commented May 14, 2026

#2216

What

Adds child-context support to Amazon.Lambda.DurableExecution on top of #2360. A user function can now run inside a logical sub-workflow with its own deterministic operation-ID space; the result is checkpointed as a CONTEXT operation so subsequent invocations replay the cached value without re-executing the func.

Public API:

Type Purpose
IDurableContext.RunInChildContextAsync<T>(...) Run a user func inside a child context, return T.
IDurableContext.RunInChildContextAsync(...) Void overload.
ChildContextConfig Optional config: SubType (observability label) + ErrorMapping (exception remapping).
ChildContextException Surfaced on child-context failure; carries SubType, ErrorType, ErrorData, OriginalStackTrace.

How

Internal/ChildContextOperation<T> mirrors the Step/Wait pattern from #2360:

  • Fresh execution. Synchronously flushes a CONTEXT START checkpoint, then invokes the user func with a child IDurableContext. The child has its own operation-ID counter, so steps/waits inside it get deterministic IDs scoped to the child. On success the SDK writes SUCCEED with the serialized result; on throw it writes FAIL with the error details and throws ChildContextException.
  • Replay. SUCCEEDED returns the cached value (deserialized via the registered ILambdaSerializer). FAILED reconstructs and throws ChildContextException (after ErrorMapping, if provided). STARTED / PENDING re-runs the func and lets the child's own operations replay from their own checkpoints — a child whose inner step suspended will re-suspend the same way.
  • Result serialization uses ILambdaContext.Serializer (consistent with StepOperation);
  • Error mapping is applied both on first failure and on replay of a FAILED checkpoint, so callers see the same exception type either way.
  • LambdaDurableServiceClient.MapFromSdkOperation is extended to copy ContextDetails (Result + Error) for the new CONTEXT operation kind.

This is a building block for upcoming WaitForCallbackAsync, which (per the Java/JS reference SDKs) wraps CreateCallbackAsync + a submitter step inside a child context for observability and clean error mapping.

Testing

14 new unit tests in ChildContextOperationTests.cs:

  • Fresh execution + checkpoint emission (CONTEXT START -> user func -> SUCCEED).
  • Deterministic child operation IDs (child's inner steps get IDs scoped to the child).
  • Replay SUCCEEDED returns cached value without re-running.
  • Replay FAILED throws ChildContextException, with and without ErrorMapping.
  • Suspended child: Wait inside child propagates termination correctly.
  • Replay STARTED with completed inner step.
  • Void overload.
  • Type-mismatch non-determinism detection.
  • SubType propagation to the wire.

161/161 tests pass on net8.0 and net10.0. Production build clean: 0 warnings, 0 errors.

Out of scope (follow-up PRs)

  • WaitForCallbackAsync / CreateCallbackAsync / InvokeAsync
  • MapAsync / ParallelAsync / WaitForConditionAsync
  • DefaultJsonCheckpointSerializer
  • DurableLogger replay-suppression (currently NullLogger)
  • Annotations source-generator integration / [DurableExecution] attribute
  • DurableTestRunner / Amazon.Lambda.DurableExecution.Testing package
  • dotnet new lambda.DurableFunction blueprint

@GarrettBeatty GarrettBeatty force-pushed the GarrettBeatty/stack/3 branch from d943d16 to 7ca2099 Compare May 14, 2026 18:07
@GarrettBeatty GarrettBeatty force-pushed the gcbeatty/durable-child-context branch 2 times, most recently from e146869 to 369a029 Compare May 14, 2026 21:49
@GarrettBeatty GarrettBeatty force-pushed the GarrettBeatty/stack/3 branch from 7ca2099 to 5a29b3e Compare May 17, 2026 20:16
@GarrettBeatty GarrettBeatty force-pushed the gcbeatty/durable-child-context branch from 369a029 to 5a29b3e Compare May 17, 2026 20:27
@GarrettBeatty GarrettBeatty reopened this May 17, 2026
@GarrettBeatty GarrettBeatty requested a review from Copilot May 17, 2026 20:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a RunInChildContextAsync API to IDurableContext enabling user functions to be executed inside a logical sub-workflow whose result is checkpointed as a CONTEXT operation. The child context shares the parent's state, termination manager, batcher, and Lambda context, but uses a child OperationIdGenerator so its operation IDs are deterministically namespaced. Failures are surfaced via a new ChildContextException, optionally remapped via ChildContextConfig.ErrorMapping. This is positioned as a building block for a future WaitForCallbackAsync.

Changes:

  • New ChildContextOperation<T> mirroring the Step/Wait pattern with replay branches for SUCCEEDED, FAILED, STARTED/PENDING.
  • Public surface additions: IDurableContext.RunInChildContextAsync (typed and void overloads), ChildContextConfig, ChildContextException.
  • Service-client mapping extended to copy ContextDetails (Result, partial Error) from SDK responses, plus 14 new unit tests.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
Libraries/src/Amazon.Lambda.DurableExecution/IDurableContext.cs Adds typed/void RunInChildContextAsync to the public interface.
Libraries/src/Amazon.Lambda.DurableExecution/DurableContext.cs Implements RunInChildContextAsync and constructs ChildContextOperation with a child-context factory.
Libraries/src/Amazon.Lambda.DurableExecution/Internal/ChildContextOperation.cs New operation type implementing fresh execution, replay, and failure paths.
Libraries/src/Amazon.Lambda.DurableExecution/ChildContextConfig.cs New config type with SubType and ErrorMapping.
Libraries/src/Amazon.Lambda.DurableExecution/DurableExecutionException.cs Adds ChildContextException with SubType, ErrorType, ErrorData, OriginalStackTrace.
Libraries/src/Amazon.Lambda.DurableExecution/Services/LambdaDurableServiceClient.cs Maps ContextDetails from the SDK operation; drops ErrorData/StackTrace.
Libraries/test/Amazon.Lambda.DurableExecution.Tests/ChildContextOperationTests.cs 14 tests covering fresh, replay, suspension, error-mapping, and non-determinism paths.
Libraries/test/Amazon.Lambda.DurableExecution.Tests/LambdaDurableServiceClientTests.cs New test for ContextDetails Result/Error copy.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@GarrettBeatty GarrettBeatty force-pushed the GarrettBeatty/stack/3 branch 6 times, most recently from 8e29ff3 to 097e5a1 Compare May 20, 2026 15:37
@GarrettBeatty GarrettBeatty force-pushed the gcbeatty/durable-child-context branch 2 times, most recently from 8058cdf to 4132f5d Compare May 20, 2026 16:34

COPY bin/publish/ ${LAMBDA_TASK_ROOT}

ENTRYPOINT ["/var/task/bootstrap"]

COPY bin/publish/ ${LAMBDA_TASK_ROOT}

ENTRYPOINT ["/var/task/bootstrap"]
@GarrettBeatty GarrettBeatty force-pushed the gcbeatty/durable-child-context branch 2 times, most recently from 59ed6b8 to 9f45bf3 Compare May 20, 2026 16:43
@GarrettBeatty GarrettBeatty requested a review from Copilot May 20, 2026 16:46

COPY bin/publish/ ${LAMBDA_TASK_ROOT}

ENTRYPOINT ["/var/task/bootstrap"]
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

Libraries/src/Amazon.Lambda.DurableExecution/Internal/StepOperation.cs:283

  • On first-time step failure (non-replay), the thrown StepException only sets ErrorType. Since the FAIL checkpoint includes stack trace (and may include ErrorData), replay will surface OriginalStackTrace/ErrorData but fresh execution will not, which is inconsistent with the StepException contract (“original error details”). Consider populating these properties from the same error payload you checkpoint so callers see consistent metadata across fresh vs replay failures.
        await EnqueueAsync(new SdkOperationUpdate
        {
            Id = OperationId,
            ParentId = ParentId,
            Type = OperationTypes.Step,
            Action = OperationAction.FAIL,
            SubType = OperationSubTypes.Step,
            Name = Name,
            Error = ToSdkError(ex)
        }, cancellationToken);

        throw new StepException(ex.Message, ex)
        {
            ErrorType = ex.GetType().FullName
        };

@GarrettBeatty GarrettBeatty added the Release Not Needed Add this label if a PR does not need to be released. label May 20, 2026
@GarrettBeatty GarrettBeatty force-pushed the gcbeatty/durable-child-context branch from 9f45bf3 to 1f53406 Compare May 20, 2026 17:00
{
ErrorType = sdkOp.StepDetails.Error.ErrorType,
ErrorMessage = sdkOp.StepDetails.Error.ErrorMessage
ErrorMessage = sdkOp.StepDetails.Error.ErrorMessage,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not related to child context, but missed copying the error details before here

/// Configuration for a child context.
/// </summary>
/// <remarks>
/// A child context is a logical sub-workflow with its own deterministic
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ctx.StepAsync(..., "validate");                 // root  #1
ctx.RunInChildContextAsync(child => {           // root  #2  (the CONTEXT op)
    child.StepAsync(..., "charge");             // child #1
    child.StepAsync(..., "ship");               // child #2
}, name: "phase");
ctx.StepAsync(..., "notify");                   // root  #3

Resulting IDs:

Op Id ParentId
validate h("1") null
phase h("2") null
charge h("h("2")-1") h("2")
ship h("h("2")-2") h("2")
notify h("3") null

Adds child-context support to the .NET Durable Execution SDK. A child
context is a logical sub-workflow with its own deterministic
operation-ID space, persisted as a CONTEXT operation so subsequent
invocations replay the cached value without re-executing the function.

Public surface:
- IDurableContext.RunInChildContextAsync<T> (reflection + AOT-safe
  ICheckpointSerializer<T> overloads, plus a void overload).
- ChildContextConfig with SubType (observability label) and
  ErrorMapping (transform exceptions before they surface to the caller).
- ChildContextException for failure surfacing.

Used as a building block for upcoming WaitForCallbackAsync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@GarrettBeatty GarrettBeatty force-pushed the gcbeatty/durable-child-context branch from c4ea83e to 607f219 Compare May 20, 2026 17:31
@GarrettBeatty GarrettBeatty marked this pull request as ready for review May 20, 2026 17:31
@GarrettBeatty GarrettBeatty requested review from a team as code owners May 20, 2026 17:31
@GarrettBeatty GarrettBeatty requested review from normj and philasmar and removed request for a team May 20, 2026 17:31
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Release Not Needed Add this label if a PR does not need to be released.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants