Skip to content

Add CreateCallbackAsync and WaitForCallbackAsync (DOTNET-8660)#2373

Draft
GarrettBeatty wants to merge 1 commit into
gcbeatty/durable-wave0from
gcbeatty/durable-callbacks
Draft

Add CreateCallbackAsync and WaitForCallbackAsync (DOTNET-8660)#2373
GarrettBeatty wants to merge 1 commit into
gcbeatty/durable-wave0from
gcbeatty/durable-callbacks

Conversation

@GarrettBeatty
Copy link
Copy Markdown
Contributor

Summary

Adds callback support to the .NET Durable Execution SDK:

  • CreateCallbackAsync<T> returns an ICallback<T> handle that suspends the workflow until an external system delivers a result via the durable execution service.
  • WaitForCallbackAsync<T> composes CreateCallback + a submitter step + GetResultAsync inside a child context for the common "submit and wait" pattern.

Stacked on top of #2372 (Wave 0 cross-cutting types).

Fixes DOTNET-8660.

Public surface

  • IDurableContext.CreateCallbackAsync<T> (reflection + AOT-safe overloads)
  • IDurableContext.WaitForCallbackAsync<T> (reflection + AOT-safe overloads)
  • ICallback<T> with CallbackId and GetResultAsync
  • IWaitForCallbackContext (Logger only) for submitter functions
  • CallbackConfig (Timeout + HeartbeatTimeout, validates sub-second values)
  • WaitForCallbackConfig : CallbackConfig adds RetryStrategy
  • Exception subclass tree: CallbackException base + CallbackFailedException, CallbackTimeoutException, CallbackSubmitterException

Internal

  • CallbackOperation<T> handles fresh execution sync-flush of START with service-allocated CallbackId, deferred error propagation, and replay for SUCCEEDED / FAILED / TIMED_OUT / STARTED / PENDING. Unknown statuses throw NonDeterministicExecutionException.
  • LambdaDurableServiceClient gains an onNewOperations callback so the freshly-allocated CallbackId from NewExecutionState flows back into ExecutionState during the START flush.
  • WaitForCallback's error mapping preserves subclass fidelity on parent-CONTEXT-FAILED replay (CallbackTimeoutException remains CallbackTimeoutException, etc.).

Test plan

  • Build clean (zero warnings, TreatWarningsAsErrors enforced) on net8.0 and net10.0
  • 43 new unit tests pass alongside existing 165
  • 5 new integration tests build successfully (require AWS credentials to run): CreateCallbackHappyPath, CallbackTimeout, CallbackFailed, WaitForCallbackHappyPath, WaitForCallbackSubmitterFails

🤖 Generated with Claude Code


COPY bin/publish/ ${LAMBDA_TASK_ROOT}

ENTRYPOINT ["/var/task/bootstrap"]

COPY bin/publish/ ${LAMBDA_TASK_ROOT}

ENTRYPOINT ["/var/task/bootstrap"]

COPY bin/publish/ ${LAMBDA_TASK_ROOT}

ENTRYPOINT ["/var/task/bootstrap"]

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-wave0 branch from 464c591 to d308c3b Compare May 14, 2026 21:49
@GarrettBeatty GarrettBeatty force-pushed the gcbeatty/durable-callbacks branch from 503666b to 951fcd1 Compare May 14, 2026 21:49
Adds callback support to the .NET Durable Execution SDK. CreateCallbackAsync
returns an ICallback<T> handle (CallbackId + GetResultAsync) that suspends
the workflow until an external system delivers a result via the durable
execution service. WaitForCallbackAsync composes CreateCallback + a
submitter step + GetResultAsync inside a child context for the common
"submit and wait" pattern.

Public surface:
- IDurableContext.CreateCallbackAsync<T> (reflection + AOT-safe overloads)
- IDurableContext.WaitForCallbackAsync<T> (reflection + AOT-safe overloads)
- ICallback<T> with CallbackId and GetResultAsync
- IWaitForCallbackContext (Logger only) for submitter functions
- CallbackConfig (Timeout + HeartbeatTimeout, validates sub-second values)
- WaitForCallbackConfig : CallbackConfig adds RetryStrategy
- Exception subclass tree: CallbackException base + CallbackFailedException,
  CallbackTimeoutException, CallbackSubmitterException

Internal:
- CallbackOperation<T> handles fresh execution sync-flush of START with
  service-allocated CallbackId, deferred error propagation, and replay
  for SUCCEEDED/FAILED/TIMED_OUT/STARTED/PENDING. Unknown statuses throw
  NonDeterministicExecutionException.
- LambdaDurableServiceClient gains an onNewOperations callback so the
  freshly-allocated CallbackId from NewExecutionState flows back into
  ExecutionState during the START flush.
- WaitForCallback's error mapping preserves subclass fidelity on
  parent-CONTEXT-FAILED replay (CallbackTimeoutException remains
  CallbackTimeoutException, etc.).

Adds 43 unit tests + 5 integration tests covering happy path, timeout,
failure, submitter failure, replay determinism, and replay of each
exception subtype.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@GarrettBeatty GarrettBeatty force-pushed the gcbeatty/durable-callbacks branch from 951fcd1 to 1c88461 Compare May 14, 2026 22:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants