Skip to content

Add ParallelAsync for concurrent branch execution (DOTNET-8662)#2375

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

Add ParallelAsync for concurrent branch execution (DOTNET-8662)#2375
GarrettBeatty wants to merge 1 commit into
gcbeatty/durable-wave0from
gcbeatty/durable-parallel

Conversation

@GarrettBeatty
Copy link
Copy Markdown
Contributor

Summary

Adds parallel branch execution to the .NET Durable Execution SDK. ParallelAsync runs N branches concurrently with configurable concurrency limits and completion policies, returning an IBatchResult<T> with per-branch status and error information.

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

Fixes DOTNET-8662.

The shared IBatchResult<T> family added here will be reused by MapAsync (Wave 2).

Public surface

  • IDurableContext.ParallelAsync<T> (4 overloads: reflection × 2 for Func[] vs DurableBranch<T>[]; AOT-safe × 2 same)
  • DurableBranch<T> record (Name + Func)
  • ParallelConfig (MaxConcurrency, CompletionConfig, NestingType)
  • CompletionConfig with factories AllSuccessful() / FirstSuccessful() / AllCompleted(); ToleratedFailureCount / ToleratedFailurePercentage (validated 0.0–1.0)
  • IBatchResult<T> with All / Succeeded / Failed / Started accessors, GetResults, GetErrors, ThrowIfError, HasFailure, CompletionReason, count properties
  • IBatchItem<T> with Index, Name, Status, Result, Error
  • BatchItemStatus { Succeeded, Failed, Started }
  • CompletionReason { AllCompleted, MinSuccessfulReached, FailureToleranceExceeded }
  • NestingType (Nested default; Flat throws NotSupportedException — reserved for a follow-up)
  • ParallelException (carries IBatchResult; future-subclassable)

Internal

  • ParallelOperation<T> orchestrator dispatches branches with optional semaphore-bounded concurrency. Each branch runs as a ChildContextOperation<T> with a deterministic ID via OperationIdGenerator.CreateChild.
  • Branch failures aggregated as IBatchItem<T> entries; orchestrator throws ParallelException only when CompletionConfig signals FailureToleranceExceeded.
  • ExecutionState now thread-safe (lock around reads/writes of _operations, _visitedOperations, _isReplaying). Required for concurrent branch replay; affects all operations but no regressions.
  • ParallelOperation awaits Task.WhenAll(inFlight) before disposing the semaphore so cancellation/exception during dispatch lets in-flight branches settle cleanly.
  • Reuses OperationSubTypes.Parallel / OperationSubTypes.ParallelBranch from Wave 0.

Test plan

  • Build clean (zero warnings, TreatWarningsAsErrors enforced) on net8.0 and net10.0
  • 33 new unit tests pass alongside existing 165, including:
    • CompletionConfig matrix (AllSuccessful, AllCompleted, FirstSuccessful, MinSuccessful, ToleratedFailureCount, ToleratedFailurePercentage)
    • Cancel-mid-dispatch regression test (no orphan branches)
    • Concurrent ExecutionState access regression test
    • Replay determinism, mixed-status replay, FirstSuccessful all-fail
  • 6 new integration tests build successfully (require AWS credentials to run)

🤖 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"]

COPY bin/publish/ ${LAMBDA_TASK_ROOT}

ENTRYPOINT ["/var/task/bootstrap"]
@GarrettBeatty GarrettBeatty force-pushed the gcbeatty/durable-parallel branch from 19c0128 to fa13eef Compare May 14, 2026 21:49
@GarrettBeatty GarrettBeatty force-pushed the gcbeatty/durable-wave0 branch from 464c591 to d308c3b Compare May 14, 2026 21:49
Adds parallel branch execution to the .NET Durable Execution SDK.
ParallelAsync runs N branches concurrently with configurable concurrency
limits and completion policies, returning an IBatchResult<T> with
per-branch status and error information.

Public surface:
- IDurableContext.ParallelAsync<T> (4 overloads: reflection x 2 for
  Func[] vs DurableBranch<T>[]; AOT-safe x 2 same)
- DurableBranch<T> record (Name + Func)
- ParallelConfig (MaxConcurrency, CompletionConfig, NestingType)
- CompletionConfig with factories AllSuccessful() / FirstSuccessful() /
  AllCompleted(); ToleratedFailureCount / ToleratedFailurePercentage
  (validated 0.0-1.0)
- IBatchResult<T> with All / Succeeded / Failed / Started accessors,
  GetResults, GetErrors, ThrowIfError, HasFailure, CompletionReason,
  count properties
- IBatchItem<T> with Index, Name, Status, Result, Error
- BatchItemStatus { Succeeded, Failed, Started }
- CompletionReason { AllCompleted, MinSuccessfulReached,
  FailureToleranceExceeded }
- NestingType (Nested default; Flat throws NotSupportedException - reserved)
- ParallelException (carries IBatchResult; future-subclassable)

Internal:
- ParallelOperation<T> orchestrator dispatches branches with optional
  semaphore-bounded concurrency. Each branch runs as a
  ChildContextOperation<T> with deterministic ID via
  OperationIdGenerator.CreateChild.
- Branch failures aggregated as IBatchItem<T> entries; orchestrator
  throws ParallelException only when CompletionConfig signals
  FailureToleranceExceeded.
- Parent CONTEXT checkpoint records summary (CompletionReason +
  per-branch index/name/status); branch results live on per-branch
  CONTEXT checkpoints.
- ExecutionState now thread-safe (lock around reads/writes of
  _operations, _visitedOperations, _isReplaying). Required for
  concurrent branch replay; affects all operations but no regressions.
- ParallelOperation awaits Task.WhenAll(inFlight) before disposing
  the semaphore so cancellation/exception during dispatch lets
  in-flight branches settle cleanly.
- Reuses OperationSubTypes.Parallel / OperationSubTypes.ParallelBranch
  from Wave 0.

Adds 33 unit tests + 6 integration tests covering CompletionConfig
matrix, MaxConcurrency, FirstSuccessful short-circuit, replay
determinism, mixed-status replay, cancellation, and concurrency
stress.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@GarrettBeatty GarrettBeatty force-pushed the gcbeatty/durable-parallel branch from fa13eef to b7a06b4 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