From 498a6160fd3647dc2597028270b1be6560e748f7 Mon Sep 17 00:00:00 2001 From: Sophia Tevosyan Date: Tue, 30 Dec 2025 15:38:55 -0800 Subject: [PATCH 01/10] first commit --- .../Shims/TaskOrchestrationContextWrapper.cs | 3 +- .../OrchestrationPatterns.cs | 62 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/Worker/Core/Shims/TaskOrchestrationContextWrapper.cs b/src/Worker/Core/Shims/TaskOrchestrationContextWrapper.cs index 945d6ac5..98ba2721 100644 --- a/src/Worker/Core/Shims/TaskOrchestrationContextWrapper.cs +++ b/src/Worker/Core/Shims/TaskOrchestrationContextWrapper.cs @@ -226,7 +226,8 @@ public override async Task CallSubOrchestratorAsync( version, instanceId, policy.ToDurableTaskCoreRetryOptions(), - input); + input, + options.Tags); } else if (options?.Retry?.Handler is AsyncRetryHandler handler) { diff --git a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs index d52c271c..313b6739 100644 --- a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs +++ b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs @@ -113,6 +113,68 @@ public async Task ScheduleSubOrchestrationWithTags() Assert.Equal("value2", metadata.Tags["tag2"]); } + [Fact] + public async Task ScheduleSubOrchestrationWithTagsAndRetryPolicy() + { + TaskName orchestratorName = nameof(ScheduleSubOrchestrationWithTags); + + // Schedule a new orchestration instance with tags + SubOrchestrationOptions subOrchestrationOptions = new() + { + InstanceId = "instance_id", + Tags = new Dictionary + { + { "tag1", "value1" }, + { "tag2", "value2" } + }, + Retry = new RetryPolicy(maxNumberOfAttempts: 2, firstRetryInterval: TimeSpan.FromSeconds(3)) + }; + + int failCounter = 0; + await using HostTestLifetime server = await this.StartWorkerAsync(b => + { + b.AddTasks(tasks => tasks.AddOrchestratorFunc(orchestratorName, async (ctx, input) => + { + if (failCounter < 1 && input == 2) + { + failCounter++; + throw new Exception("Simulated failure"); + } + + int result = 1; + if (input < 2) + { + // recursively call this same orchestrator + result += await ctx.CallSubOrchestratorAsync(orchestratorName, input: input + 1, subOrchestrationOptions); + } + + return result; + })); + }); + + // Confirm the first attempt failed + await server.Client.ScheduleNewOrchestrationInstanceAsync(orchestratorName, input: 1); + OrchestrationMetadata metadata = await server.Client.WaitForInstanceCompletionAsync( + subOrchestrationOptions.InstanceId, this.TimeoutToken); + Assert.NotNull(metadata); + Assert.Equal(subOrchestrationOptions.InstanceId, metadata.InstanceId); + Assert.Equal(OrchestrationRuntimeStatus.Failed, metadata.RuntimeStatus); + + // Give some time for the retry to happen + Thread.Sleep(5000); + + // Confirm the second attempt succeeded + metadata = await server.Client.WaitForInstanceCompletionAsync( + subOrchestrationOptions.InstanceId, this.TimeoutToken); + Assert.NotNull(metadata); + Assert.Equal(subOrchestrationOptions.InstanceId, metadata.InstanceId); + Assert.Equal(OrchestrationRuntimeStatus.Completed, metadata.RuntimeStatus); + Assert.NotNull(metadata.Tags); + Assert.Equal(2, metadata.Tags.Count); + Assert.Equal("value1", metadata.Tags["tag1"]); + Assert.Equal("value2", metadata.Tags["tag2"]); + } + [Fact] public async Task SingleTimer() { From ab530c054b3bb491e216e22b5a9ab2bc7cd5fd6c Mon Sep 17 00:00:00 2001 From: Sophia Tevosyan Date: Tue, 30 Dec 2025 16:25:51 -0800 Subject: [PATCH 02/10] updated the unit test --- .../OrchestrationPatterns.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs index 313b6739..c620285c 100644 --- a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs +++ b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs @@ -105,7 +105,6 @@ public async Task ScheduleSubOrchestrationWithTags() subOrchestrationOptions.InstanceId, this.TimeoutToken); Assert.NotNull(metadata); - Assert.Equal(subOrchestrationOptions.InstanceId, metadata.InstanceId); Assert.Equal(OrchestrationRuntimeStatus.Completed, metadata.RuntimeStatus); Assert.NotNull(metadata.Tags); Assert.Equal(2, metadata.Tags.Count); @@ -127,7 +126,7 @@ public async Task ScheduleSubOrchestrationWithTagsAndRetryPolicy() { "tag1", "value1" }, { "tag2", "value2" } }, - Retry = new RetryPolicy(maxNumberOfAttempts: 2, firstRetryInterval: TimeSpan.FromSeconds(3)) + Retry = new RetryPolicy(maxNumberOfAttempts: 2, firstRetryInterval: TimeSpan.FromSeconds(5)) }; int failCounter = 0; @@ -157,17 +156,17 @@ public async Task ScheduleSubOrchestrationWithTagsAndRetryPolicy() OrchestrationMetadata metadata = await server.Client.WaitForInstanceCompletionAsync( subOrchestrationOptions.InstanceId, this.TimeoutToken); Assert.NotNull(metadata); - Assert.Equal(subOrchestrationOptions.InstanceId, metadata.InstanceId); Assert.Equal(OrchestrationRuntimeStatus.Failed, metadata.RuntimeStatus); - // Give some time for the retry to happen - Thread.Sleep(5000); + // Wait for the retry to happen + while (metadata.RuntimeStatus != OrchestrationRuntimeStatus.Completed && !this.TimeoutToken.IsCancellationRequested) + { + await Task.Delay(TimeSpan.FromSeconds(1), this.TimeoutToken); + metadata = await server.Client.WaitForInstanceCompletionAsync( + subOrchestrationOptions.InstanceId, this.TimeoutToken); + } // Confirm the second attempt succeeded - metadata = await server.Client.WaitForInstanceCompletionAsync( - subOrchestrationOptions.InstanceId, this.TimeoutToken); - Assert.NotNull(metadata); - Assert.Equal(subOrchestrationOptions.InstanceId, metadata.InstanceId); Assert.Equal(OrchestrationRuntimeStatus.Completed, metadata.RuntimeStatus); Assert.NotNull(metadata.Tags); Assert.Equal(2, metadata.Tags.Count); From 26deb092c8118fe9c18f34416e62f5a6ed3c3093 Mon Sep 17 00:00:00 2001 From: Sophia Tevosyan Date: Tue, 30 Dec 2025 16:26:10 -0800 Subject: [PATCH 03/10] missed something --- test/Grpc.IntegrationTests/OrchestrationPatterns.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs index c620285c..cdc52cac 100644 --- a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs +++ b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs @@ -115,7 +115,7 @@ public async Task ScheduleSubOrchestrationWithTags() [Fact] public async Task ScheduleSubOrchestrationWithTagsAndRetryPolicy() { - TaskName orchestratorName = nameof(ScheduleSubOrchestrationWithTags); + TaskName orchestratorName = nameof(ScheduleSubOrchestrationWithTagsAndRetryPolicy); // Schedule a new orchestration instance with tags SubOrchestrationOptions subOrchestrationOptions = new() From 4584351a335ea506b5cae541ba93509d023ddf36 Mon Sep 17 00:00:00 2001 From: Sophia Tevosyan Date: Tue, 30 Dec 2025 16:33:04 -0800 Subject: [PATCH 04/10] comment update --- test/Grpc.IntegrationTests/OrchestrationPatterns.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs index cdc52cac..7bfb4337 100644 --- a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs +++ b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs @@ -117,7 +117,7 @@ public async Task ScheduleSubOrchestrationWithTagsAndRetryPolicy() { TaskName orchestratorName = nameof(ScheduleSubOrchestrationWithTagsAndRetryPolicy); - // Schedule a new orchestration instance with tags + // Schedule a new orchestration instance with tags and a retry policy SubOrchestrationOptions subOrchestrationOptions = new() { InstanceId = "instance_id", From 5d0093700b155117c17bb86d4b65f9df187f870f Mon Sep 17 00:00:00 2001 From: Sophia Tevosyan Date: Mon, 5 Jan 2026 14:04:26 -0800 Subject: [PATCH 05/10] dependency update --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index b3045aeb..80be6c90 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -31,7 +31,7 @@ - + From 823b8b08d64a99798caeb4efe032c16481c8ce31 Mon Sep 17 00:00:00 2001 From: Sophia Tevosyan Date: Mon, 5 Jan 2026 14:17:48 -0800 Subject: [PATCH 06/10] attempting to fix the flaky test --- test/Grpc.IntegrationTests/OrchestrationPatterns.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs index 7bfb4337..b590ff36 100644 --- a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs +++ b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Diagnostics; using System.Text.Json; using System.Text.Json.Nodes; using Microsoft.DurableTask.Client; @@ -126,7 +127,7 @@ public async Task ScheduleSubOrchestrationWithTagsAndRetryPolicy() { "tag1", "value1" }, { "tag2", "value2" } }, - Retry = new RetryPolicy(maxNumberOfAttempts: 2, firstRetryInterval: TimeSpan.FromSeconds(5)) + Retry = new RetryPolicy(maxNumberOfAttempts: 2, firstRetryInterval: TimeSpan.FromSeconds(15)) }; int failCounter = 0; @@ -150,20 +151,21 @@ public async Task ScheduleSubOrchestrationWithTagsAndRetryPolicy() return result; })); }); + CancellationTokenSource timeoutTokenSource = new(TimeSpan.FromMinutes(1)); // Confirm the first attempt failed await server.Client.ScheduleNewOrchestrationInstanceAsync(orchestratorName, input: 1); OrchestrationMetadata metadata = await server.Client.WaitForInstanceCompletionAsync( - subOrchestrationOptions.InstanceId, this.TimeoutToken); + subOrchestrationOptions.InstanceId, timeoutTokenSource.Token); Assert.NotNull(metadata); Assert.Equal(OrchestrationRuntimeStatus.Failed, metadata.RuntimeStatus); // Wait for the retry to happen - while (metadata.RuntimeStatus != OrchestrationRuntimeStatus.Completed && !this.TimeoutToken.IsCancellationRequested) + while (metadata.RuntimeStatus != OrchestrationRuntimeStatus.Completed && !timeoutTokenSource.Token.IsCancellationRequested) { - await Task.Delay(TimeSpan.FromSeconds(1), this.TimeoutToken); + await Task.Delay(TimeSpan.FromSeconds(1), timeoutTokenSource.Token); metadata = await server.Client.WaitForInstanceCompletionAsync( - subOrchestrationOptions.InstanceId, this.TimeoutToken); + subOrchestrationOptions.InstanceId, timeoutTokenSource.Token); } // Confirm the second attempt succeeded From aa3b7bb5ba211e030d40aa8acbaa1c03c554030f Mon Sep 17 00:00:00 2001 From: sophiatev <38052607+sophiatev@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:22:26 -0800 Subject: [PATCH 07/10] Potential fix for pull request finding 'Missing Dispose call on local IDisposable' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- test/Grpc.IntegrationTests/OrchestrationPatterns.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs index b590ff36..5b9cd83c 100644 --- a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs +++ b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs @@ -151,7 +151,7 @@ public async Task ScheduleSubOrchestrationWithTagsAndRetryPolicy() return result; })); }); - CancellationTokenSource timeoutTokenSource = new(TimeSpan.FromMinutes(1)); + using CancellationTokenSource timeoutTokenSource = new(TimeSpan.FromMinutes(1)); // Confirm the first attempt failed await server.Client.ScheduleNewOrchestrationInstanceAsync(orchestratorName, input: 1); From 4b44c3e6c9cc5e161ff48355d1c820874230e4d7 Mon Sep 17 00:00:00 2001 From: Sophia Tevosyan Date: Mon, 5 Jan 2026 14:31:57 -0800 Subject: [PATCH 08/10] second attempt --- .../OrchestrationPatterns.cs | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs index 5b9cd83c..0033c12a 100644 --- a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs +++ b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs @@ -127,14 +127,19 @@ public async Task ScheduleSubOrchestrationWithTagsAndRetryPolicy() { "tag1", "value1" }, { "tag2", "value2" } }, - Retry = new RetryPolicy(maxNumberOfAttempts: 2, firstRetryInterval: TimeSpan.FromSeconds(15)) + Retry = new RetryPolicy(maxNumberOfAttempts: 2, firstRetryInterval: TimeSpan.FromMilliseconds(500)) }; int failCounter = 0; + int invocationCounter = 0; await using HostTestLifetime server = await this.StartWorkerAsync(b => { b.AddTasks(tasks => tasks.AddOrchestratorFunc(orchestratorName, async (ctx, input) => { + if (!ctx.IsReplaying) + { + invocationCounter++; + } if (failCounter < 1 && input == 2) { failCounter++; @@ -151,29 +156,21 @@ public async Task ScheduleSubOrchestrationWithTagsAndRetryPolicy() return result; })); }); - using CancellationTokenSource timeoutTokenSource = new(TimeSpan.FromMinutes(1)); - // Confirm the first attempt failed await server.Client.ScheduleNewOrchestrationInstanceAsync(orchestratorName, input: 1); + + // Confirm the orchestration eventually succeeded after some delay for the retry to complete + await Task.Delay(TimeSpan.FromSeconds(3)); OrchestrationMetadata metadata = await server.Client.WaitForInstanceCompletionAsync( - subOrchestrationOptions.InstanceId, timeoutTokenSource.Token); + subOrchestrationOptions.InstanceId, this.TimeoutToken); Assert.NotNull(metadata); - Assert.Equal(OrchestrationRuntimeStatus.Failed, metadata.RuntimeStatus); - - // Wait for the retry to happen - while (metadata.RuntimeStatus != OrchestrationRuntimeStatus.Completed && !timeoutTokenSource.Token.IsCancellationRequested) - { - await Task.Delay(TimeSpan.FromSeconds(1), timeoutTokenSource.Token); - metadata = await server.Client.WaitForInstanceCompletionAsync( - subOrchestrationOptions.InstanceId, timeoutTokenSource.Token); - } - - // Confirm the second attempt succeeded Assert.Equal(OrchestrationRuntimeStatus.Completed, metadata.RuntimeStatus); Assert.NotNull(metadata.Tags); Assert.Equal(2, metadata.Tags.Count); Assert.Equal("value1", metadata.Tags["tag1"]); Assert.Equal("value2", metadata.Tags["tag2"]); + // 3 invocations - one for the parent orchestration, one for the first suborchestration attempt, one for the final suborchestration attempt + Assert.Equal(3, invocationCounter); } [Fact] From d440c6d703d74155cc96ebe886c790c435fdcdd9 Mon Sep 17 00:00:00 2001 From: sophiatev <38052607+sophiatev@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:48:17 -0800 Subject: [PATCH 09/10] Update test/Grpc.IntegrationTests/OrchestrationPatterns.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- test/Grpc.IntegrationTests/OrchestrationPatterns.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs index 0033c12a..8d7b6610 100644 --- a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs +++ b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Diagnostics; using System.Text.Json; using System.Text.Json.Nodes; using Microsoft.DurableTask.Client; From 3a8b323a22442ed18bc28568096dd005574c60b9 Mon Sep 17 00:00:00 2001 From: Sophia Tevosyan Date: Mon, 5 Jan 2026 15:24:24 -0800 Subject: [PATCH 10/10] another fix for an old test --- test/Grpc.IntegrationTests/OrchestrationPatterns.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs index 8d7b6610..1114029b 100644 --- a/test/Grpc.IntegrationTests/OrchestrationPatterns.cs +++ b/test/Grpc.IntegrationTests/OrchestrationPatterns.cs @@ -59,7 +59,6 @@ public async Task ScheduleOrchestrationWithTags() instanceId, this.TimeoutToken); Assert.NotNull(metadata); - Assert.Equal(instanceId, metadata.InstanceId); Assert.Equal(OrchestrationRuntimeStatus.Completed, metadata.RuntimeStatus); Assert.NotNull(metadata.Tags); Assert.Equal(2, metadata.Tags.Count);