Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8357e15
first commit?
Jan 21, 2026
b434062
PR comments
Jan 21, 2026
b8fc30b
Merge branch 'main' into stevosyan/remove-default-dedupe-statuses
Jan 21, 2026
c54e0cb
updating documentation
Jan 26, 2026
47bcb33
Merge branch 'main' into stevosyan/remove-default-dedupe-statuses
Jan 26, 2026
d8c26b3
added implementation to shim client too
Jan 26, 2026
64c3ba9
added tests for the shim client
Jan 26, 2026
796f522
PR comments
Jan 26, 2026
87a9bec
reverted the logic to not include a reuse policy in the case that ded…
Jan 28, 2026
b6c7145
updating the tests accordingly
Jan 29, 2026
02b2427
PR comments
Feb 4, 2026
dedcf9e
addressing PR comments
Feb 4, 2026
0b9a89e
Merge branch 'main' into stevosyan/remove-default-dedupe-statuses
Feb 4, 2026
098d9a3
updated tests to check for new exception type
Feb 4, 2026
c10c1bf
slight comment update
Feb 5, 2026
212d1aa
Adding an ArgumentException for invalid dedupe statuses (any running …
Feb 11, 2026
acddee1
added support to terminate existing running instances for restart
Feb 11, 2026
68d84a3
addressing copilot comments
Feb 11, 2026
edf3564
addressing the PR comments and build warnings
Feb 20, 2026
50b4ee9
Merge branch 'main' into stevosyan/remove-default-dedupe-statuses
Feb 20, 2026
6eb7b51
fixed the build errors
Feb 20, 2026
e600a5a
missed a change in comment type in GrpcDurableTaskClient
Feb 20, 2026
bf426ec
missed updating a comment in the tests
Feb 20, 2026
50e42d3
returned the catching of the RpcException with status code cancelled,…
Feb 20, 2026
820d069
Merge branch 'main' into stevosyan/remove-default-dedupe-statuses
sophiatev Feb 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions src/Client/Core/DurableTaskClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,11 @@
/// <remarks>
/// <para>All orchestrations must have a unique instance ID. You can provide an instance ID using the
/// <paramref name="options"/> parameter or you can omit this and a random instance ID will be
/// generated for you automatically. If an orchestration with the specified instance ID already exists and is in a
/// non-terminal state (Pending, Running, etc.), then this operation may fail silently. However, if an orchestration
/// instance with this ID already exists in a terminal state (Completed, Terminated, Failed, etc.) then the instance
/// may be recreated automatically, depending on the configuration of the backend instance store.
/// generated for you automatically. If an orchestration with the specified instance ID already exists and its status
/// is not in the <see cref="StartOrchestrationOptions.DedupeStatuses"/> field of <paramref name="options"/>, then
/// a new orchestration may be recreated automatically, depending on the configuration of the backend instance store.
Comment thread
sophiatev marked this conversation as resolved.
/// If the existing orchestration is in a non-terminal state (Pending, Running, etc.), then the orchestration will first
/// be terminated before the new orchestration is created.
Comment thread
sophiatev marked this conversation as resolved.
/// </para><para>
Comment thread
sophiatev marked this conversation as resolved.
/// Orchestration instances started with this method will be created in the
/// <see cref="OrchestrationRuntimeStatus.Pending"/> state and will transition to the
Expand All @@ -98,8 +99,9 @@
/// </param>
/// <param name="options">The options to start the new orchestration with.</param>
/// <param name="cancellation">
/// The cancellation token. This only cancels enqueueing the new orchestration to the backend. Does not cancel the
/// orchestration once enqueued.
/// The cancellation token. This only cancels enqueueing the new orchestration to the backend, or waiting for the
/// termination of an existing non-terminal instance if its status is not in
/// <see cref="StartOrchestrationOptions.DedupeStatuses"/>. Does not cancel the orchestration once enqueued.
/// </param>
/// <returns>
/// A task that completes when the orchestration instance is successfully scheduled. The value of this task is
Expand Down Expand Up @@ -529,7 +531,7 @@
throw new NotSupportedException(
$"{this.GetType()} does not support listing orchestration instance IDs filtered by completed time.");
}

Check warning on line 534 in src/Client/Core/DurableTaskClient.cs

View workflow job for this annotation

GitHub Actions / smoke-tests

Check warning on line 534 in src/Client/Core/DurableTaskClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

// TODO: Create task hub

// TODO: Delete task hub
Expand All @@ -539,3 +541,3 @@
/// </summary>
/// <returns>A <see cref="ValueTask"/> that completes when the disposal completes.</returns>
public abstract ValueTask DisposeAsync();
Expand Down
23 changes: 7 additions & 16 deletions src/Client/Grpc/GrpcDurableTaskClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,9 @@ public override async Task<string> ScheduleNewOrchestrationInstanceAsync(
}

// Set orchestration ID reuse policy for deduplication support
// Note: This requires the protobuf to support OrchestrationIdReusePolicy field
// If the protobuf doesn't support it yet, this will need to be updated when the protobuf is updated
if (options?.DedupeStatuses != null && options.DedupeStatuses.Count > 0)
{
// Parse and validate all status strings to enum first
ImmutableHashSet<OrchestrationRuntimeStatus> dedupeStatuses = options.DedupeStatuses
ImmutableHashSet<OrchestrationRuntimeStatus> dedupeStatuses = options?.DedupeStatuses is null
? []
: [.. options.DedupeStatuses
.Select(s =>
{
if (!System.Enum.TryParse<OrchestrationRuntimeStatus>(s, ignoreCase: true, out OrchestrationRuntimeStatus status))
Expand All @@ -139,17 +136,11 @@ public override async Task<string> ScheduleNewOrchestrationInstanceAsync(
}

return status;
}).ToImmutableHashSet();

// Convert dedupe statuses to protobuf statuses and create reuse policy
IEnumerable<P.OrchestrationStatus> dedupeStatusesProto = dedupeStatuses.Select(s => s.ToGrpcStatus());
P.OrchestrationIdReusePolicy? policy = ProtoUtils.ConvertDedupeStatusesToReusePolicy(dedupeStatusesProto);
})];

if (policy != null)
{
request.OrchestrationIdReusePolicy = policy;
}
}
// Convert dedupe statuses to protobuf statuses and create reuse policy
IEnumerable<P.OrchestrationStatus> dedupeStatusesProto = dedupeStatuses.Select(s => s.ToGrpcStatus());
request.OrchestrationIdReusePolicy = ProtoUtils.ConvertDedupeStatusesToReusePolicy(dedupeStatusesProto);
Comment thread
sophiatev marked this conversation as resolved.
Outdated

using Activity? newActivity = TraceHelper.StartActivityForNewOrchestration(request);

Expand Down
52 changes: 25 additions & 27 deletions src/Client/Grpc/ProtoUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,22 @@ namespace Microsoft.DurableTask.Client.Grpc;
public static class ProtoUtils
{
/// <summary>
/// Gets the terminal orchestration statuses that are commonly used for deduplication.
/// Gets an array of all orchestration statuses.
/// These are the statuses that can be used in OrchestrationIdReusePolicy.
/// </summary>
/// <returns>An immutable array of terminal orchestration statuses.</returns>
public static ImmutableArray<P.OrchestrationStatus> GetTerminalStatuses()
/// <returns>An immutable array of all orchestration statuses.</returns>
public static ImmutableArray<P.OrchestrationStatus> GetAllStatuses()
Comment thread
sophiatev marked this conversation as resolved.
{
#pragma warning disable CS0618 // Type or member is obsolete - Canceled is intentionally included for compatibility
#pragma warning disable CS0618 // Type or member is obsolete - Canceled is intentionally included for compatibility
// compatibility with what?
Comment thread
sophiatev marked this conversation as resolved.
Outdated
Comment thread
sophiatev marked this conversation as resolved.
Outdated
return ImmutableArray.Create(
P.OrchestrationStatus.Completed,
P.OrchestrationStatus.Failed,
P.OrchestrationStatus.Terminated,
P.OrchestrationStatus.Canceled);
P.OrchestrationStatus.Terminated,
P.OrchestrationStatus.Canceled,
P.OrchestrationStatus.Pending,
P.OrchestrationStatus.Running,
P.OrchestrationStatus.Suspended);
Comment thread
sophiatev marked this conversation as resolved.
#pragma warning restore CS0618
}

Expand All @@ -33,55 +37,49 @@ public static class ProtoUtils
/// with replaceable statuses (statuses that CAN be replaced).
/// </summary>
/// <param name="dedupeStatuses">The orchestration statuses that should NOT be replaced. These are statuses for which an exception should be thrown if an orchestration already exists.</param>
/// <returns>An OrchestrationIdReusePolicy with replaceable statuses set, or null if all terminal statuses are dedupe statuses.</returns>
/// <returns>An OrchestrationIdReusePolicy with replaceable statuses set.</returns>
/// <remarks>
/// The policy uses "replaceableStatus" - these are statuses that CAN be replaced.
/// dedupeStatuses are statuses that should NOT be replaced.
/// So replaceableStatus = all terminal statuses MINUS dedupeStatuses.
/// So replaceableStatus = all statuses MINUS dedupeStatuses.
/// </remarks>
public static P.OrchestrationIdReusePolicy? ConvertDedupeStatusesToReusePolicy(
public static P.OrchestrationIdReusePolicy ConvertDedupeStatusesToReusePolicy(
IEnumerable<P.OrchestrationStatus>? dedupeStatuses)
{
ImmutableArray<P.OrchestrationStatus> terminalStatuses = GetTerminalStatuses();
ImmutableArray<P.OrchestrationStatus> statuses = GetAllStatuses();
ImmutableHashSet<P.OrchestrationStatus> dedupeStatusSet = dedupeStatuses?.ToImmutableHashSet() ?? ImmutableHashSet<P.OrchestrationStatus>.Empty;

Comment thread
sophiatev marked this conversation as resolved.
P.OrchestrationIdReusePolicy policy = new();

// Add terminal statuses that are NOT in dedupeStatuses as replaceable
foreach (P.OrchestrationStatus terminalStatus in terminalStatuses.Where(status => !dedupeStatusSet.Contains(status)))
// Add statuses that are NOT in dedupeStatuses as replaceable
foreach (P.OrchestrationStatus status in statuses.Where(status => !dedupeStatusSet.Contains(status)))
{
policy.ReplaceableStatus.Add(terminalStatus);
policy.ReplaceableStatus.Add(status);
}

// Only return policy if we have replaceable statuses
return policy.ReplaceableStatus.Count > 0 ? policy : null;
return policy;
}
Comment thread
sophiatev marked this conversation as resolved.

/// <summary>
/// Converts an OrchestrationIdReusePolicy with replaceable statuses to dedupe statuses
/// (statuses that should NOT be replaced).
/// </summary>
/// <param name="policy">The OrchestrationIdReusePolicy containing replaceable statuses.</param>
/// <returns>An array of orchestration statuses that should NOT be replaced, or null if all terminal statuses are replaceable.</returns>
/// <returns>An array of orchestration statuses that should NOT be replaced, or null if all statuses are replaceable.</returns>
/// <remarks>
/// The policy uses "replaceableStatus" - these are statuses that CAN be replaced.
/// dedupeStatuses are statuses that should NOT be replaced (should throw exception).
/// So dedupeStatuses = all terminal statuses MINUS replaceableStatus.
/// So dedupeStatuses = all statuses MINUS replaceableStatus.
/// </remarks>
public static P.OrchestrationStatus[]? ConvertReusePolicyToDedupeStatuses(
P.OrchestrationIdReusePolicy? policy)
P.OrchestrationIdReusePolicy policy)
{
Comment thread
sophiatev marked this conversation as resolved.
Outdated
if (policy == null || policy.ReplaceableStatus.Count == 0)
{
return null;
}

ImmutableArray<P.OrchestrationStatus> terminalStatuses = GetTerminalStatuses();
ImmutableArray<P.OrchestrationStatus> allStatuses = GetAllStatuses();
ImmutableHashSet<P.OrchestrationStatus> replaceableStatusSet = policy.ReplaceableStatus.ToImmutableHashSet();

// Calculate dedupe statuses = terminal statuses - replaceable statuses
P.OrchestrationStatus[] dedupeStatuses = terminalStatuses
.Where(terminalStatus => !replaceableStatusSet.Contains(terminalStatus))
// Calculate dedupe statuses = all statuses - replaceable statuses
P.OrchestrationStatus[] dedupeStatuses = allStatuses
.Where(status => !replaceableStatusSet.Contains(status))
.ToArray();

// Only return if there are dedupe statuses
Expand Down
2 changes: 1 addition & 1 deletion src/InProcessTestHost/Sidecar/Grpc/TaskHubGrpcServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ async Task WaitForWorkItemClientConnection()
// Convert OrchestrationIdReusePolicy to dedupeStatuses
// The policy uses "replaceableStatus" - these are statuses that CAN be replaced
// dedupeStatuses are statuses that should NOT be replaced (should throw exception)
// So dedupeStatuses = all terminal statuses MINUS replaceableStatus
// So dedupeStatuses = all statuses MINUS replaceableStatus
OrchestrationStatus[]? dedupeStatuses = null;
Comment thread
sophiatev marked this conversation as resolved.
P.OrchestrationStatus[]? dedupeStatusesProto = ProtoUtils.ConvertReusePolicyToDedupeStatuses(request.OrchestrationIdReusePolicy);
if (dedupeStatusesProto != null)
Expand Down
Loading
Loading