feat(samples): demonstrate durable Orleans workflows - #10699
ReubenBond wants to merge 137 commits into
Conversation
41f8dfe to
c26c119
Compare
|
CI failure analysis: Documentation run 32341516041 failed deterministically because the new DurableWorkflows sample restores System.Distributed.DurableTasks, but the sample-validation package feed did not contain that intentionally unpublished project (NU1101). Commit 82ac313 makes the dependency packable only for sample validation and includes its README in the temporary package; the sample build and full solution build now succeed, and fresh CI has been triggered by the push. |
|
A fresh Documentation run exposed a second PR-specific validation failure after the sample build passed: the generated gallery linked to the DurableWorkflows directory on the main branch before that directory exists there, producing a 404. Commit db02c5d adds the established exact temporary external-link allowlist entry; it will become stale automatically after merge. The complete docs validation and solution build now pass. |
There was a problem hiding this comment.
Review details
Suppressed comments (1)
src/Orleans.DurableJobs/ShardExecutor.cs:313
ResettableJobShardNotSupportedExceptionis excluded from the inner catch, so it bubbles to the outer catch and gets enqueued intotaskFailures(causingRunShardAsyncto fail the whole shard) instead of being routed through_options.ShouldRetry(...)like other execution failures.
If a job handler accidentally returns RetryAt while using a shard implementation which cannot reschedule, this turns into a shard-level failure rather than a job-level failure with backoff. Consider treating this exception as failureException (so it follows the normal retry/no-retry path) or converting it into a Failed result path with a clear log message.
- Files reviewed: 140/168 changed files
- Comments generated: 2
- Review effort level: Lite
There was a problem hiding this comment.
Review details
Suppressed comments (2)
src/Microsoft.Orleans.DurableTasks/DurableTaskMessageHandler.cs:25
IDurableTaskRequest.Contextis nullable; using the null-forgiving operator here can turn malformed/mixed-version messages into aNullReferenceException. Consider validatinginvocation.Request.Contextand throwing anInvalidOperationExceptionwith a clear message if it's missing.
src/Microsoft.Orleans.DurableTasks/Storage/DurableTaskHostingExtensions.cs:11#pragma warning disable CS1998appears unnecessary in this file (there are noasyncmethods). This can trigger IDE0079 ('Remove unnecessary suppression') under analyzers-as-errors.
- Files reviewed: 137/168 changed files
- Comments generated: 3
- Review effort level: Lite
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
A newly introduced public serialized DTO (DurableTaskDiagnosticState) declares non-nullable reference members which can be null in version-tolerant/default scenarios, requiring an API contract fix (and regenerated API surface).
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Lite
Findings: 1
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
src/Microsoft.Orleans.DurableTasks/Protocol/IDurableTaskGrainRuntime.cs — DurableTaskDiagnosticState is a version-tolerant serialized struct, so reference-typed members… |
Suppressed comments (1)
docs/site/src/content/docs/resources/nuget-packages.md:82
- This table links to
https://www.nuget.org/packages/Microsoft.Orleans.DurableMessaging, but the same PR metadata indicates the package is not yet published (and it’s listed as unpublished indocs/site/src/data/unpublished-api-packages.json). Consider removing the NuGet link and explicitly marking the package as awaiting first publication, consistent with the DurableTasks entries above.
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
It introduces new runtime infrastructure and public surface area across multiple subsystems (Journaling, Durable Jobs, Durable Messaging, Durable Tasks) in addition to the sample and CI wiring, requiring careful end-to-end validation and human review.
Review tier: Lite
Findings: 2
New issues introduced by this change (2)
| Severity | Finding |
|---|---|
samples/DurableWorkflows/DurableWorkflows.Service/WorkflowGrain.cs — RunOrderSagaAsync returns exception.Message in the public OrderWorkflowResult failure field.… |
|
src/Orleans.DurableMessaging/DurableMessagingActivationValidator.cs — Validate uses reflection + LINQ over all grain interfaces/methods on every activation where… |
Issues resolved since last review (1)
| Severity | Finding |
|---|---|
src/Microsoft.Orleans.DurableTasks/Protocol/IDurableTaskGrainRuntime.cs — DurableTaskDiagnosticState is a version-tolerant serialized struct, so reference-typed members… View resolved comment |
| _ = await inventory.ReleaseAsync($"{request.OrderId}:release").WithId("release-inventory"); | ||
| compensations.Add("release-inventory"); | ||
| } | ||
|
|
||
| return new(request.OrderId, OrderWorkflowStatus.Compensated, compensations.ToArray(), exception.Message); | ||
| } |
| var grainInterfaces = grainType | ||
| .GetInterfaces() | ||
| .Where(static type => typeof(IGrain).IsAssignableFrom(type)) | ||
| .ToArray(); | ||
| var interleavableMethod = grainInterfaces | ||
| .SelectMany(static type => type.GetInterfaces().Append(type)) | ||
| .Distinct() | ||
| .SelectMany(static type => type.GetMethods()) | ||
| .FirstOrDefault(static method => method.IsDefined(typeof(AlwaysInterleaveAttribute), inherit: true)); |

Problem
The durable execution stack needs a curated, copyable sample showing how its public APIs compose across Orleans replicas, including recovery, external decisions, compensation, and retained-result observation.
Solution
Adds
samples/DurableWorkflows, an Aspire application with two service replicas, Redis membership, and Azurite-backed Journaling and Durable Jobs storage. The sample demonstrates stable root and child identities, durable fan-out/fan-in, human approval, persisted business cancellation, reverse-order saga compensation, and idempotent effects.Status GETs attach to existing tasks and poll retained results. Missing or expired roots return 404; retained execution failures return a safe failed status, including failures caused by another grain's missing task with the same root ID. Recovery scenarios wait on the original scheduled handle after activation or silo loss.
The sample includes 23 workflow/endpoint cases, gallery registration, standalone project/package declarations, and guidance distinguishing result retention, retained task identity, and message deduplication. Documentation CI builds the local Orleans packages and executes the sample's MTP test application against them, preserving cluster logs for recovery diagnosis.
Package boundary and publication
The alpha
Microsoft.Orleans.DurableTasks.AbstractionsandMicrosoft.Orleans.DurableMessagingfoundations participate in normal solution packaging. Sample validation explicitly opts into the incubatingMicrosoft.Orleans.DurableTasksadapter withPackDurableTaskAdapter=true.During repository validation,
Build-Samples.ps1selects one unique local package version.samples/Directory.Build.targetsappliesOrleansSamplePackageVersionto everyMicrosoft.Orleans.*package reference and central package version. This locks the entire Orleans package family to that source build. The checked-in standalone version declarations identify the individual packages' release channels; repository validation overrides them with the exact local version.Standalone public-feed restore requires publication of every required package. The adapter's publication decision remains a dependency of this sample's release.
Dependency history
The branch preserves exact audited dependency ancestry and adds sample-only commits above it:
2a07eafec3c0eee6267e0396d9ec3adce468cf68.f70d8040de6756f14571b7eb4ae2896d7767f340.b3cef3bb5f3018bbf0483bdd7378b161bbab3286.757db97d4c5fec67162c6a881ce9b9ee8638b5b5.The sample-owned delta from #10696 is restricted to
samples/and its Documentation CI execution and diagnostics steps. Generic Journaling participant and observer regressions remain in the inherited Messaging layer. The PR base staysmainso upstream CI runs while the fork dependencies are unmerged.