From 6c41917ae326cbf7b619665eb39f07e704914b81 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Wed, 7 May 2025 13:13:56 -0700 Subject: [PATCH 01/21] add tag to TaskScheduledEvent --- src/DurableTask.Core/History/TaskScheduledEvent.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/DurableTask.Core/History/TaskScheduledEvent.cs b/src/DurableTask.Core/History/TaskScheduledEvent.cs index c0f9108d3..dab60fd93 100644 --- a/src/DurableTask.Core/History/TaskScheduledEvent.cs +++ b/src/DurableTask.Core/History/TaskScheduledEvent.cs @@ -84,5 +84,11 @@ public TaskScheduledEvent(int eventId) /// [DataMember] public DistributedTraceContext? ParentTraceContext { get; set; } + + /// + /// Gets or sets a dictionary of tags of string, string + /// + [DataMember] + public IDictionary Tags { get; set; } = new Dictionary(); } } \ No newline at end of file From ef1255a354d3558d6d17e8b2c1c4d1fe48a882f1 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Wed, 7 May 2025 13:28:00 -0700 Subject: [PATCH 02/21] more changes --- .../Command/ScheduleTaskOrchestratorAction.cs | 7 +++++ src/DurableTask.Core/DurableTask.Core.csproj | 2 +- .../History/TaskScheduledEvent.cs | 3 +- src/DurableTask.Core/OrchestrationContext.cs | 29 +++++++++++++++++++ .../OrchestrationRuntimeState.cs | 1 + .../TaskOrchestrationContext.cs | 29 ++++++++++++++----- .../TaskOrchestrationDispatcher.cs | 9 ++++-- 7 files changed, 67 insertions(+), 13 deletions(-) diff --git a/src/DurableTask.Core/Command/ScheduleTaskOrchestratorAction.cs b/src/DurableTask.Core/Command/ScheduleTaskOrchestratorAction.cs index f4c65b84c..4e3c0bf85 100644 --- a/src/DurableTask.Core/Command/ScheduleTaskOrchestratorAction.cs +++ b/src/DurableTask.Core/Command/ScheduleTaskOrchestratorAction.cs @@ -11,6 +11,8 @@ // limitations under the License. // ---------------------------------------------------------------------------------- #nullable enable +using System.Collections.Generic; + namespace DurableTask.Core.Command { /// @@ -41,5 +43,10 @@ public class ScheduleTaskOrchestratorAction : OrchestratorAction // TODO: This property is not used and should be removed or made obsolete internal string? Tasklist { get; set; } + + /// + /// Gets or sets a dictionary of tags of string, string + /// + public IDictionary? Tags { get; set; } } } \ No newline at end of file diff --git a/src/DurableTask.Core/DurableTask.Core.csproj b/src/DurableTask.Core/DurableTask.Core.csproj index 4bedc92a8..d50486c61 100644 --- a/src/DurableTask.Core/DurableTask.Core.csproj +++ b/src/DurableTask.Core/DurableTask.Core.csproj @@ -18,7 +18,7 @@ 3 1 - 0 + 1 $(MajorVersion).$(MinorVersion).$(PatchVersion) $(VersionPrefix).0 diff --git a/src/DurableTask.Core/History/TaskScheduledEvent.cs b/src/DurableTask.Core/History/TaskScheduledEvent.cs index dab60fd93..73f84ce15 100644 --- a/src/DurableTask.Core/History/TaskScheduledEvent.cs +++ b/src/DurableTask.Core/History/TaskScheduledEvent.cs @@ -13,6 +13,7 @@ #nullable enable namespace DurableTask.Core.History { + using System.Collections.Generic; using System.Runtime.Serialization; using DurableTask.Core.Tracing; @@ -89,6 +90,6 @@ public TaskScheduledEvent(int eventId) /// Gets or sets a dictionary of tags of string, string /// [DataMember] - public IDictionary Tags { get; set; } = new Dictionary(); + public IDictionary? Tags { get; set; } } } \ No newline at end of file diff --git a/src/DurableTask.Core/OrchestrationContext.cs b/src/DurableTask.Core/OrchestrationContext.cs index 4b2d8c7fe..95c810854 100644 --- a/src/DurableTask.Core/OrchestrationContext.cs +++ b/src/DurableTask.Core/OrchestrationContext.cs @@ -206,6 +206,22 @@ public virtual Task ScheduleWithRetry(string name, string version, RetryOp var retryInterceptor = new RetryInterceptor(this, retryOptions, RetryCall); return retryInterceptor.Invoke(); } + + /// + /// Schedule a TaskActivity by type. Also retry on failure as per supplied policy. + /// + /// Return Type of the TaskActivity.Execute method + /// Name of the orchestration as specified by the ObjectCreator + /// Name of the orchestration as specified by the ObjectCreator + /// Retry policy + /// Dictionary of key/value tags associated with this instance + /// Parameters for the TaskActivity.Execute method + /// Task that represents the execution of the specified TaskActivity + public virtual Task ScheduleWithRetry(string name, string version, RetryOptions retryOptions, + IDictionary tags, params object[] parameters) + { + throw new NotImplementedException(); + } /// /// Create a sub-orchestration of the specified type. Also retry on failure as per supplied policy. @@ -286,6 +302,19 @@ public virtual Task ScheduleTask(Type activityType, params obj NameVersionHelper.GetDefaultVersion(activityType), parameters); } + /// + /// Schedule a TaskActivity by type, version, and tags. + /// + /// Return Type of the TaskActivity.Execute method + /// Name of the orchestration as specified by the ObjectCreator + /// Name of the orchestration as specified by the ObjectCreator + /// Parameters for the TaskActivity.Execute method + /// Dictionary of key/value tags associated with this instance + public virtual Task ScheduleTask(string name, string version, IDictionary tags, params object[] parameters) + { + throw new NotImplementedException(); + } + /// /// Schedule a TaskActivity by name and version. /// diff --git a/src/DurableTask.Core/OrchestrationRuntimeState.cs b/src/DurableTask.Core/OrchestrationRuntimeState.cs index 494071dbd..3c10fe693 100644 --- a/src/DurableTask.Core/OrchestrationRuntimeState.cs +++ b/src/DurableTask.Core/OrchestrationRuntimeState.cs @@ -368,6 +368,7 @@ HistoryEvent GenerateAbridgedEvent(HistoryEvent evt) Name = taskScheduledEvent.Name, Version = taskScheduledEvent.Version, Input = "[..snipped..]", + Tags = taskScheduledEvent.Tags, }; } else if (evt is TaskCompletedEvent taskCompletedEvent) diff --git a/src/DurableTask.Core/TaskOrchestrationContext.cs b/src/DurableTask.Core/TaskOrchestrationContext.cs index 7908eeb07..bf561bbef 100644 --- a/src/DurableTask.Core/TaskOrchestrationContext.cs +++ b/src/DurableTask.Core/TaskOrchestrationContext.cs @@ -78,18 +78,32 @@ internal void ClearPendingActions() continueAsNew = null; } - public override async Task ScheduleTask(string name, string version, - params object[] parameters) + public override async Task ScheduleTask(string name, string version, + IDictionary tags, params object[] parameters) { - TResult result = await ScheduleTaskToWorker(name, version, null, parameters); + TResult result = await ScheduleTaskToWorker(name, version, null, tags, parameters); return result; } - public async Task ScheduleTaskToWorker(string name, string version, string taskList, + public override async Task ScheduleTask(string name, string version, params object[] parameters) { - object result = await ScheduleTaskInternal(name, version, taskList, typeof(TResult), parameters); + return await ScheduleTask(name, version, null, parameters); + } + + public override async Task ScheduleWithRetry(string name, string version, + RetryOptions retryOptions, IDictionary tags, params object[] parameters) + { + Task RetryCall() => ScheduleTask(name, version, tags:tags, parameters:parameters); + var retryInterceptor = new RetryInterceptor(this, retryOptions, RetryCall); + return await retryInterceptor.Invoke(); + } + + public async Task ScheduleTaskToWorker(string name, string version, string taskList, + IDictionary tags, params object[] parameters) + { + object result = await ScheduleTaskInternal(name, version, taskList, typeof(TResult), tags:tags, parameters:parameters); if (result == null) { @@ -99,8 +113,7 @@ public async Task ScheduleTaskToWorker(string name, string ver return (TResult)result; } - public async Task ScheduleTaskInternal(string name, string version, string taskList, Type resultType, - params object[] parameters) + public async Task ScheduleTaskInternal(string name, string version, string taskList, Type resultType, IDictionary tags, params object[] parameters) { int id = this.idCounter++; string serializedInput = this.MessageDataConverter.SerializeInternal(parameters); @@ -111,8 +124,8 @@ public async Task ScheduleTaskInternal(string name, string version, stri Version = version, Tasklist = taskList, Input = serializedInput, + Tags = tags, }; - this.orchestratorActionsMap.Add(id, scheduleTaskTaskAction); var tcs = new TaskCompletionSource(); diff --git a/src/DurableTask.Core/TaskOrchestrationDispatcher.cs b/src/DurableTask.Core/TaskOrchestrationDispatcher.cs index d1cb03f87..64f926f7a 100644 --- a/src/DurableTask.Core/TaskOrchestrationDispatcher.cs +++ b/src/DurableTask.Core/TaskOrchestrationDispatcher.cs @@ -1050,12 +1050,13 @@ TaskMessage ProcessScheduleTaskDecision( } var taskMessage = new TaskMessage(); - var scheduledEvent = new TaskScheduledEvent( eventId: scheduleTaskOrchestratorAction.Id, name: scheduleTaskOrchestratorAction.Name, version: scheduleTaskOrchestratorAction.Version, - input: scheduleTaskOrchestratorAction.Input); + input: scheduleTaskOrchestratorAction.Input) { + Tags = scheduleTaskOrchestratorAction.Tags + }; ActivitySpanId clientSpanId = ActivitySpanId.CreateRandom(); @@ -1074,7 +1075,9 @@ TaskMessage ProcessScheduleTaskDecision( scheduledEvent = new TaskScheduledEvent( eventId: scheduleTaskOrchestratorAction.Id, name: scheduleTaskOrchestratorAction.Name, - version: scheduleTaskOrchestratorAction.Version); + version: scheduleTaskOrchestratorAction.Version) { + Tags = scheduleTaskOrchestratorAction.Tags + }; if (parentTraceActivity != null) { From 57f2847af23653e5ad0909b5668c4f0ff29e947d Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Wed, 14 May 2025 14:11:32 -0700 Subject: [PATCH 03/21] fb builder pattern --- src/DurableTask.Core/OrchestrationContext.cs | 20 +-- src/DurableTask.Core/ScheduleTaskOptions.cs | 143 ++++++++++++++++++ .../TaskOrchestrationContext.cs | 52 ++++--- .../ScheduleTaskTests.cs | 5 +- 4 files changed, 182 insertions(+), 38 deletions(-) create mode 100644 src/DurableTask.Core/ScheduleTaskOptions.cs diff --git a/src/DurableTask.Core/OrchestrationContext.cs b/src/DurableTask.Core/OrchestrationContext.cs index 95c810854..97c6f6f6d 100644 --- a/src/DurableTask.Core/OrchestrationContext.cs +++ b/src/DurableTask.Core/OrchestrationContext.cs @@ -206,22 +206,6 @@ public virtual Task ScheduleWithRetry(string name, string version, RetryOp var retryInterceptor = new RetryInterceptor(this, retryOptions, RetryCall); return retryInterceptor.Invoke(); } - - /// - /// Schedule a TaskActivity by type. Also retry on failure as per supplied policy. - /// - /// Return Type of the TaskActivity.Execute method - /// Name of the orchestration as specified by the ObjectCreator - /// Name of the orchestration as specified by the ObjectCreator - /// Retry policy - /// Dictionary of key/value tags associated with this instance - /// Parameters for the TaskActivity.Execute method - /// Task that represents the execution of the specified TaskActivity - public virtual Task ScheduleWithRetry(string name, string version, RetryOptions retryOptions, - IDictionary tags, params object[] parameters) - { - throw new NotImplementedException(); - } /// /// Create a sub-orchestration of the specified type. Also retry on failure as per supplied policy. @@ -309,8 +293,8 @@ public virtual Task ScheduleTask(Type activityType, params obj /// Name of the orchestration as specified by the ObjectCreator /// Name of the orchestration as specified by the ObjectCreator /// Parameters for the TaskActivity.Execute method - /// Dictionary of key/value tags associated with this instance - public virtual Task ScheduleTask(string name, string version, IDictionary tags, params object[] parameters) + /// Options for scheduling a task + public virtual Task ScheduleTask(string name, string version, ScheduleTaskOptions options, params object[] parameters) { throw new NotImplementedException(); } diff --git a/src/DurableTask.Core/ScheduleTaskOptions.cs b/src/DurableTask.Core/ScheduleTaskOptions.cs new file mode 100644 index 000000000..8791106af --- /dev/null +++ b/src/DurableTask.Core/ScheduleTaskOptions.cs @@ -0,0 +1,143 @@ +// ---------------------------------------------------------------------------------- +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +#nullable enable + +namespace DurableTask.Core +{ + using System; + using System.Collections.Generic; + + /// + /// Options for scheduling a task. + /// + public class ScheduleTaskOptions + { + /// + /// Initializes a new instance of the class. + /// + protected ScheduleTaskOptions() + { + } + + /// + /// Dictionary of key/value tags associated with this instance. + /// + public IDictionary? Tags { get; internal set; } + + /// + /// Gets or sets the retry options for the scheduled task. + /// + public RetryOptions? RetryOptions { get; internal set; } + + /// + /// Creates a new builder for constructing a instance. + /// + /// A new builder for creating schedule task options. + public static Builder CreateBuilder() + { + return new Builder(); + } + + /// + /// Builder class for creating instances of . + /// + public class Builder + { + private readonly ScheduleTaskOptions options; + + /// + /// Initializes a new instance of the class. + /// + internal Builder() + { + this.options = new ScheduleTaskOptions(); + } + + /// + /// Sets the tags for the schedule task options. + /// + /// The dictionary of key/value tags. + /// The builder instance. + public Builder WithTags(IDictionary tags) + { + this.options.Tags = tags; + return this; + } + + /// + /// Adds a tag to the schedule task options. + /// + /// The tag key. + /// The tag value. + /// The builder instance. + public Builder AddTag(string key, string value) + { + if (this.options.Tags == null) + { + this.options.Tags = new Dictionary(); + } + + this.options.Tags[key] = value; + return this; + } + + /// + /// Sets the retry options for the scheduled task. + /// + /// The retry options to use. + /// The builder instance. + public Builder WithRetryOptions(RetryOptions retryOptions) + { + this.options.RetryOptions = retryOptions; + return this; + } + + /// + /// Sets the retry options for the scheduled task with the specified parameters. + /// + /// Timespan to wait for the first retry. + /// Max number of attempts to retry. + /// The builder instance. + public Builder WithRetryOptions(TimeSpan firstRetryInterval, int maxNumberOfAttempts) + { + this.options.RetryOptions = new RetryOptions(firstRetryInterval, maxNumberOfAttempts); + return this; + } + + /// + /// Sets the retry options for the scheduled task with the specified parameters and configures additional properties. + /// + /// Timespan to wait for the first retry. + /// Max number of attempts to retry. + /// Action to configure additional retry option properties. + /// The builder instance. + public Builder WithRetryOptions(TimeSpan firstRetryInterval, int maxNumberOfAttempts, Action configureRetryOptions) + { + var retryOptions = new RetryOptions(firstRetryInterval, maxNumberOfAttempts); + configureRetryOptions?.Invoke(retryOptions); + this.options.RetryOptions = retryOptions; + return this; + } + + /// + /// Builds the instance. + /// + /// The built schedule task options. + public ScheduleTaskOptions Build() + { + return this.options; + } + } + } +} \ No newline at end of file diff --git a/src/DurableTask.Core/TaskOrchestrationContext.cs b/src/DurableTask.Core/TaskOrchestrationContext.cs index bf561bbef..b12cb1b08 100644 --- a/src/DurableTask.Core/TaskOrchestrationContext.cs +++ b/src/DurableTask.Core/TaskOrchestrationContext.cs @@ -78,32 +78,33 @@ internal void ClearPendingActions() continueAsNew = null; } - public override async Task ScheduleTask(string name, string version, - IDictionary tags, params object[] parameters) + public override async Task ScheduleTask(string name, string version, + params object[] parameters) { - TResult result = await ScheduleTaskToWorker(name, version, null, tags, parameters); + TResult result = await ScheduleTaskToWorker(name, version, null, parameters); return result; } public override async Task ScheduleTask(string name, string version, - params object[] parameters) - { - return await ScheduleTask(name, version, null, parameters); - } - - public override async Task ScheduleWithRetry(string name, string version, - RetryOptions retryOptions, IDictionary tags, params object[] parameters) + ScheduleTaskOptions options, params object[] parameters) { - Task RetryCall() => ScheduleTask(name, version, tags:tags, parameters:parameters); - var retryInterceptor = new RetryInterceptor(this, retryOptions, RetryCall); - return await retryInterceptor.Invoke(); + if (options.RetryOptions != null) + { + Task RetryCall() => ScheduleTask(name, version, ScheduleTaskOptions.CreateBuilder().WithTags(options.Tags).Build(), parameters); + var retryInterceptor = new RetryInterceptor(this, options.RetryOptions, RetryCall); + return await retryInterceptor.Invoke(); + } + + TResult result = await ScheduleTaskToWorker(name, version, null, options, parameters); + + return result; } - public async Task ScheduleTaskToWorker(string name, string version, string taskList, - IDictionary tags, params object[] parameters) + public async Task ScheduleTaskToWorker(string name, string version, string taskList, + ScheduleTaskOptions options, params object[] parameters) { - object result = await ScheduleTaskInternal(name, version, taskList, typeof(TResult), tags:tags, parameters:parameters); + object result = await ScheduleTaskInternal(name, version, taskList, typeof(TResult), options, parameters); if (result == null) { @@ -113,7 +114,14 @@ public async Task ScheduleTaskToWorker(string name, string ver return (TResult)result; } - public async Task ScheduleTaskInternal(string name, string version, string taskList, Type resultType, IDictionary tags, params object[] parameters) + public async Task ScheduleTaskToWorker(string name, string version, string taskList, + params object[] parameters) + { + return await ScheduleTaskToWorker(name, version, taskList, ScheduleTaskOptions.CreateBuilder().Build(), parameters); + } + + public async Task ScheduleTaskInternal(string name, string version, string taskList, Type resultType, + ScheduleTaskOptions options, params object[] parameters) { int id = this.idCounter++; string serializedInput = this.MessageDataConverter.SerializeInternal(parameters); @@ -124,8 +132,9 @@ public async Task ScheduleTaskInternal(string name, string version, stri Version = version, Tasklist = taskList, Input = serializedInput, - Tags = tags, + Tags = options.Tags, }; + this.orchestratorActionsMap.Add(id, scheduleTaskTaskAction); var tcs = new TaskCompletionSource(); @@ -136,6 +145,13 @@ public async Task ScheduleTaskInternal(string name, string version, stri return this.MessageDataConverter.Deserialize(serializedResult, resultType); } + + public async Task ScheduleTaskInternal(string name, string version, string taskList, Type resultType, + params object[] parameters) + { + return await ScheduleTaskInternal(name, version, taskList, resultType, ScheduleTaskOptions.CreateBuilder().Build(), parameters); + } + public override Task CreateSubOrchestrationInstance( string name, string version, diff --git a/test/DurableTask.AzureStorage.Tests/ScheduleTaskTests.cs b/test/DurableTask.AzureStorage.Tests/ScheduleTaskTests.cs index a607933f4..77c99ec57 100644 --- a/test/DurableTask.AzureStorage.Tests/ScheduleTaskTests.cs +++ b/test/DurableTask.AzureStorage.Tests/ScheduleTaskTests.cs @@ -13,9 +13,10 @@ namespace DurableTask.AzureStorage.Tests { - using System.Threading.Tasks; using DurableTask.Core; using Microsoft.VisualStudio.TestTools.UnitTesting; + using System.Collections.Generic; + using System.Threading.Tasks; [TestClass] public class ScheduleTaskTests @@ -40,7 +41,7 @@ public async Task TaskReturnsVoid_OrchestratorFails() TestInstance instance = await host.StartInlineOrchestration( input: 123, orchestrationName: "TestOrchestration", - implementation: (ctx, input) => ctx.ScheduleTask("Activity", "", input), + implementation: (ctx, input) => ctx.ScheduleTask("Activity", "", new Dictionary()), activities: ("Activity", activity)); // The expectedOutput value is the string that's passed into the InvalidOperationException From 4b07512c50566d66095201bc399660bfcbf97fe3 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Wed, 14 May 2025 15:12:27 -0700 Subject: [PATCH 04/21] some fb --- src/DurableTask.Core/DurableTask.Core.csproj | 2 +- test/DurableTask.AzureStorage.Tests/ScheduleTaskTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DurableTask.Core/DurableTask.Core.csproj b/src/DurableTask.Core/DurableTask.Core.csproj index aaf354801..26302990b 100644 --- a/src/DurableTask.Core/DurableTask.Core.csproj +++ b/src/DurableTask.Core/DurableTask.Core.csproj @@ -18,7 +18,7 @@ 3 2 - 1 + 0 $(MajorVersion).$(MinorVersion).$(PatchVersion) $(VersionPrefix).0 diff --git a/test/DurableTask.AzureStorage.Tests/ScheduleTaskTests.cs b/test/DurableTask.AzureStorage.Tests/ScheduleTaskTests.cs index 77c99ec57..c7e01b971 100644 --- a/test/DurableTask.AzureStorage.Tests/ScheduleTaskTests.cs +++ b/test/DurableTask.AzureStorage.Tests/ScheduleTaskTests.cs @@ -41,7 +41,7 @@ public async Task TaskReturnsVoid_OrchestratorFails() TestInstance instance = await host.StartInlineOrchestration( input: 123, orchestrationName: "TestOrchestration", - implementation: (ctx, input) => ctx.ScheduleTask("Activity", "", new Dictionary()), + implementation: (ctx, input) => ctx.ScheduleTask("Activity", "", input), activities: ("Activity", activity)); // The expectedOutput value is the string that's passed into the InvalidOperationException From 1e21e068f0bd3826f22afd1eb02cafd4fb72d075 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Wed, 14 May 2025 15:42:54 -0700 Subject: [PATCH 05/21] bump minor ver --- src/DurableTask.Core/DurableTask.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DurableTask.Core/DurableTask.Core.csproj b/src/DurableTask.Core/DurableTask.Core.csproj index 26302990b..6668a8a95 100644 --- a/src/DurableTask.Core/DurableTask.Core.csproj +++ b/src/DurableTask.Core/DurableTask.Core.csproj @@ -17,7 +17,7 @@ 3 - 2 + 3 0 $(MajorVersion).$(MinorVersion).$(PatchVersion) $(VersionPrefix).0 From b150b6e35c529e6691a5be022422f163086bfcfb Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Wed, 14 May 2025 15:59:00 -0700 Subject: [PATCH 06/21] scheduletaskoptions unit tests --- .../ScheduleTaskOptionsTests.cs | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 Test/DurableTask.Core.Tests/ScheduleTaskOptionsTests.cs diff --git a/Test/DurableTask.Core.Tests/ScheduleTaskOptionsTests.cs b/Test/DurableTask.Core.Tests/ScheduleTaskOptionsTests.cs new file mode 100644 index 000000000..1c79b9dbd --- /dev/null +++ b/Test/DurableTask.Core.Tests/ScheduleTaskOptionsTests.cs @@ -0,0 +1,210 @@ +// --------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// --------------------------------------------------------------- + +namespace DurableTask.Core.Tests +{ + using System; + using System.Collections.Generic; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ScheduleTaskOptionsTests + { + [TestMethod] + public void CreateBuilder_ShouldReturnBuilderInstance() + { + // Act + var builder = ScheduleTaskOptions.CreateBuilder(); + + // Assert + Assert.IsNotNull(builder); + Assert.IsInstanceOfType(builder, typeof(ScheduleTaskOptions.Builder)); + } + + [TestMethod] + public void Build_ShouldCreateInstanceWithNullProperties() + { + // Act + var options = ScheduleTaskOptions.CreateBuilder().Build(); + + // Assert + Assert.IsNotNull(options); + Assert.IsNull(options.Tags); + Assert.IsNull(options.RetryOptions); + } + + [TestMethod] + public void WithTags_ShouldSetTagsProperty() + { + // Arrange + var tags = new Dictionary + { + { "key1", "value1" }, + { "key2", "value2" } + }; + + // Act + var options = ScheduleTaskOptions.CreateBuilder() + .WithTags(tags) + .Build(); + + // Assert + Assert.IsNotNull(options.Tags); + Assert.AreEqual(2, options.Tags.Count); + Assert.AreEqual("value1", options.Tags["key1"]); + Assert.AreEqual("value2", options.Tags["key2"]); + } + + [TestMethod] + public void AddTag_WithNullTags_ShouldInitializeTagsCollection() + { + // Act + var options = ScheduleTaskOptions.CreateBuilder() + .AddTag("key1", "value1") + .Build(); + + // Assert + Assert.IsNotNull(options.Tags); + Assert.AreEqual(1, options.Tags.Count); + Assert.AreEqual("value1", options.Tags["key1"]); + } + + [TestMethod] + public void AddTag_WithExistingTags_ShouldAddToCollection() + { + // Arrange + var tags = new Dictionary + { + { "key1", "value1" } + }; + + // Act + var options = ScheduleTaskOptions.CreateBuilder() + .WithTags(tags) + .AddTag("key2", "value2") + .Build(); + + // Assert + Assert.IsNotNull(options.Tags); + Assert.AreEqual(2, options.Tags.Count); + Assert.AreEqual("value1", options.Tags["key1"]); + Assert.AreEqual("value2", options.Tags["key2"]); + } + + [TestMethod] + public void AddTag_OverwriteExistingKey_ShouldUpdateValue() + { + // Act + var options = ScheduleTaskOptions.CreateBuilder() + .AddTag("key1", "originalValue") + .AddTag("key1", "newValue") + .Build(); + + // Assert + Assert.IsNotNull(options.Tags); + Assert.AreEqual(1, options.Tags.Count); + Assert.AreEqual("newValue", options.Tags["key1"]); + } + + [TestMethod] + public void WithRetryOptions_Instance_ShouldSetRetryOptionsProperty() + { + // Arrange + var retryOptions = new RetryOptions(TimeSpan.FromSeconds(5), 3); + + // Act + var options = ScheduleTaskOptions.CreateBuilder() + .WithRetryOptions(retryOptions) + .Build(); + + // Assert + Assert.IsNotNull(options.RetryOptions); + Assert.AreEqual(retryOptions, options.RetryOptions); + Assert.AreEqual(TimeSpan.FromSeconds(5), options.RetryOptions.FirstRetryInterval); + Assert.AreEqual(3, options.RetryOptions.MaxNumberOfAttempts); + } + + [TestMethod] + public void WithRetryOptions_Parameters_ShouldCreateAndSetRetryOptions() + { + // Act + var options = ScheduleTaskOptions.CreateBuilder() + .WithRetryOptions(TimeSpan.FromSeconds(5), 3) + .Build(); + + // Assert + Assert.IsNotNull(options.RetryOptions); + Assert.AreEqual(TimeSpan.FromSeconds(5), options.RetryOptions.FirstRetryInterval); + Assert.AreEqual(3, options.RetryOptions.MaxNumberOfAttempts); + Assert.AreEqual(1, options.RetryOptions.BackoffCoefficient); + Assert.AreEqual(TimeSpan.MaxValue, options.RetryOptions.MaxRetryInterval); + } + + [TestMethod] + public void WithRetryOptions_WithConfigureAction_ShouldConfigureRetryOptions() + { + // Act + var options = ScheduleTaskOptions.CreateBuilder() + .WithRetryOptions(TimeSpan.FromSeconds(5), 3, retryOptions => + { + retryOptions.BackoffCoefficient = 2.0; + retryOptions.MaxRetryInterval = TimeSpan.FromMinutes(1); + }) + .Build(); + + // Assert + Assert.IsNotNull(options.RetryOptions); + Assert.AreEqual(TimeSpan.FromSeconds(5), options.RetryOptions.FirstRetryInterval); + Assert.AreEqual(3, options.RetryOptions.MaxNumberOfAttempts); + Assert.AreEqual(2.0, options.RetryOptions.BackoffCoefficient); + Assert.AreEqual(TimeSpan.FromMinutes(1), options.RetryOptions.MaxRetryInterval); + } + + [TestMethod] + public void WithRetryOptions_PassNullConfigureAction_ShouldStillCreateRetryOptions() + { + // Act + var options = ScheduleTaskOptions.CreateBuilder() + .WithRetryOptions(TimeSpan.FromSeconds(5), 3, null) + .Build(); + + // Assert + Assert.IsNotNull(options.RetryOptions); + Assert.AreEqual(TimeSpan.FromSeconds(5), options.RetryOptions.FirstRetryInterval); + Assert.AreEqual(3, options.RetryOptions.MaxNumberOfAttempts); + } + + [TestMethod] + public void FluentInterface_CombiningAllMethods_ShouldBuildCorrectInstance() + { + // Arrange + var retryOptions = new RetryOptions(TimeSpan.FromSeconds(1), 2); + + // Act + var options = ScheduleTaskOptions.CreateBuilder() + .AddTag("env", "test") + .WithRetryOptions(retryOptions) + .AddTag("priority", "high") + .Build(); + + // Assert + Assert.IsNotNull(options); + Assert.IsNotNull(options.Tags); + Assert.AreEqual(2, options.Tags.Count); + Assert.AreEqual("test", options.Tags["env"]); + Assert.AreEqual("high", options.Tags["priority"]); + Assert.AreEqual(retryOptions, options.RetryOptions); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void RetryOptions_WithInvalidInterval_ShouldThrowException() + { + // Act - This should throw an ArgumentException + ScheduleTaskOptions.CreateBuilder() + .WithRetryOptions(TimeSpan.Zero, 3) + .Build(); + } + } +} \ No newline at end of file From 30ef922e00daa893222be8927f40c509289ae470 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Wed, 14 May 2025 17:01:20 -0700 Subject: [PATCH 07/21] unit tests for taskorchestrationcontext --- .../TaskOrchestrationContextTests.cs | 462 ++++++++++++++++++ 1 file changed, 462 insertions(+) create mode 100644 Test/DurableTask.Core.Tests/TaskOrchestrationContextTests.cs diff --git a/Test/DurableTask.Core.Tests/TaskOrchestrationContextTests.cs b/Test/DurableTask.Core.Tests/TaskOrchestrationContextTests.cs new file mode 100644 index 000000000..f49f88af5 --- /dev/null +++ b/Test/DurableTask.Core.Tests/TaskOrchestrationContextTests.cs @@ -0,0 +1,462 @@ +// --------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// --------------------------------------------------------------- + +namespace DurableTask.Core.Tests +{ + using DurableTask.Core.Exceptions; + using DurableTask.Core.History; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + [TestClass] + public class TaskOrchestrationContextTests + { + private MockTaskOrchestrationContext context; + private OrchestrationInstance instance; + + [TestInitialize] + public void Initialize() + { + instance = new OrchestrationInstance { InstanceId = "TestInstance", ExecutionId = Guid.NewGuid().ToString() }; + context = new MockTaskOrchestrationContext(instance, TaskScheduler.Default); + } + + [TestMethod] + public async Task ScheduleTask_Basic_ShouldScheduleTask() + { + // Act + Task resultTask = context.ScheduleTask("TestActivity", "1.0", 10, 20); + Assert.IsFalse(resultTask.IsCompleted); + + // Verify task was scheduled + Assert.AreEqual(1, context.ScheduledTasks.Count); + ScheduledTaskInfo scheduledTask = context.ScheduledTasks[0]; + Assert.AreEqual("TestActivity", scheduledTask.Name); + Assert.AreEqual("1.0", scheduledTask.Version); + CollectionAssert.AreEqual(new object[] { 10, 20 }, scheduledTask.Parameters); + + // Complete the task and verify result + context.CompleteTask(0, 30); + int result = await resultTask; + Assert.AreEqual(30, result); + } + + [TestMethod] + public async Task ScheduleTask_WithNullOptions_ShouldScheduleTask() + { + // Act + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder().Build(); + Task resultTask = context.ScheduleTask("TestActivity", "1.0", options, 10, 20); + Assert.IsFalse(resultTask.IsCompleted); + + // Verify task was scheduled + Assert.AreEqual(1, context.ScheduledTasks.Count); + ScheduledTaskInfo scheduledTask = context.ScheduledTasks[0]; + Assert.AreEqual("TestActivity", scheduledTask.Name); + Assert.AreEqual("1.0", scheduledTask.Version); + CollectionAssert.AreEqual(new object[] { 10, 20 }, scheduledTask.Parameters); + Assert.IsNull(scheduledTask.Options.Tags); + Assert.IsNull(scheduledTask.Options.RetryOptions); + + // Complete the task and verify result + context.CompleteTask(0, 30); + int result = await resultTask; + Assert.AreEqual(30, result); + } + + [TestMethod] + public async Task ScheduleTask_WithTags_ShouldPassTags() + { + // Arrange + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() + .AddTag("key1", "value1") + .AddTag("key2", "value2") + .Build(); + + // Act + Task resultTask = context.ScheduleTask("TestActivity", "1.0", options, 10, 20); + + // Verify task was scheduled with tags + Assert.AreEqual(1, context.ScheduledTasks.Count); + ScheduledTaskInfo scheduledTask = context.ScheduledTasks[0]; + + Assert.IsNotNull(scheduledTask.Options.Tags); + Assert.AreEqual(2, scheduledTask.Options.Tags.Count); + Assert.AreEqual("value1", scheduledTask.Options.Tags["key1"]); + Assert.AreEqual("value2", scheduledTask.Options.Tags["key2"]); + + // Complete the task + context.CompleteTask(0, 30); + await resultTask; + } + + [TestMethod] + public async Task ScheduleTask_WithRetryOptions_ShouldRetryOnFailure() + { + // Arrange + RetryOptions retryOptions = new RetryOptions(TimeSpan.FromMilliseconds(1000), 3); + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() + .WithRetryOptions(retryOptions) + .Build(); + + // Act + Task resultTask = context.ScheduleTask("TestActivity", "1.0", options, 10, 20); + + // Fail the first attempt + context.FailTask(0, new InvalidOperationException("First failure")); + + // Verify that a retry was scheduled + Assert.AreEqual(3, context.ScheduledTasks.Count); + + // Fail the second attempt + context.FailTask(1, new InvalidOperationException("Second failure")); + + // Verify that another retry was scheduled + Assert.AreEqual(4, context.ScheduledTasks.Count); + + // Complete the third attempt successfully + context.CompleteTask(2, 30); + + // Verify result + int result = await resultTask; + Assert.AreEqual(30, result); + + // Verify all tasks had the same name, version, and parameters + foreach (ScheduledTaskInfo task in context.ScheduledTasks) + { + Assert.AreEqual("TestActivity", task.Name); + Assert.AreEqual("1.0", task.Version); + CollectionAssert.AreEqual(new object[] { 10, 20 }, task.Parameters); + } + } + + [TestMethod] + public async Task ScheduleTask_WithRetryOptions_ShouldRespectMaxRetries() + { + // Arrange + RetryOptions retryOptions = new RetryOptions(TimeSpan.FromMilliseconds(1), 2); + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() + .WithRetryOptions(retryOptions) + .Build(); + + // Act + Task resultTask = context.ScheduleTask("TestActivity", "1.0", options, 10, 20); + + // Fail all attempts + context.FailTask(0, new InvalidOperationException("First failure")); + context.FailTask(1, new InvalidOperationException("Second failure")); + + // Verify that no more retries are scheduled after max retries + Assert.AreEqual(3, context.ScheduledTasks.Count); + + // Verify the task failed + try + { + await resultTask; + Assert.Fail("Task should have failed"); + } + catch (TaskFailedException ex) + { + Assert.IsInstanceOfType(ex.InnerException, typeof(InvalidOperationException)); + Assert.AreEqual("Second failure", ex.InnerException.Message); + } + } + + [TestMethod] + public async Task ScheduleTask_WithRetryOptions_ShouldRespectRetryHandler() + { + // Arrange + RetryOptions retryOptions = new RetryOptions(TimeSpan.FromMilliseconds(1), 3) + { + Handle = ex => ex is ArgumentException // Only retry ArgumentException + }; + + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() + .WithRetryOptions(retryOptions) + .Build(); + + // Act + Task resultTask = context.ScheduleTask("TestActivity", "1.0", options, 10, 20); + + // Fail with exception that should be retried + context.FailTask(0, new ArgumentException("Should retry")); + + // Verify that a retry was scheduled + Assert.AreEqual(2, context.ScheduledTasks.Count); + + // Fail with exception that should NOT be retried + context.FailTask(1, new InvalidOperationException("Should not retry")); + + // Verify that no more retries are scheduled - but expect dummy timer for backwards compatibility + Assert.AreEqual(2, context.ScheduledTasks.Count); + + // Verify the task failed with the expected exception + try + { + await resultTask; + Assert.Fail("Task should have failed"); + } + catch (TaskFailedException ex) + { + // Since we're using RetryInterceptor, the last exception is what gets propagated + // which would be the ArgumentException from the first failure + Assert.IsInstanceOfType(ex.InnerException, typeof(ArgumentException)); + Assert.AreEqual("Should retry", ex.InnerException.Message); + } + } + + [TestMethod] + public async Task ScheduleTask_WithDefaultReturnValue_ShouldHandleNullResult() + { + // Act + Task resultTask = context.ScheduleTask("TestActivity", "1.0", 10, 20); + + // Complete the task with null result + context.CompleteTaskWithNullResult(0); + + // Verify default value is returned + int result = await resultTask; + Assert.AreEqual(0, result); // default for int is 0 + } + + [TestMethod] + public async Task ScheduleTask_WithReferenceType_ShouldHandleNullResult() + { + // Act + Task resultTask = context.ScheduleTask("TestActivity", "1.0", 10, 20); + + // Complete the task with null result + context.CompleteTaskWithNullResult(0); + + // Verify null is returned + string result = await resultTask; + Assert.IsNull(result); // default for reference type is null + } + + [TestMethod] + public async Task ScheduleTask_WithFailure_ShouldPropagateException() + { + // Act + Task resultTask = context.ScheduleTask("TestActivity", "1.0", 10, 20); + + // Fail the task + InvalidOperationException expectedException = new InvalidOperationException("Expected failure"); + context.FailTask(0, expectedException); + + // Verify exception is propagated + try + { + await resultTask; + Assert.Fail("Task should have failed"); + } + catch (TaskFailedException ex) + { + Assert.AreEqual("TestActivity", ex.Name); + Assert.AreEqual("1.0", ex.Version); + Assert.IsInstanceOfType(ex.InnerException, typeof(InvalidOperationException)); + Assert.AreEqual("Expected failure", ex.InnerException.Message); + } + } + + [TestMethod] + public async Task ScheduleTask_WithRetryBackoff_ShouldApplyBackoffPolicy() + { + // Arrange + RetryOptions retryOptions = new RetryOptions(TimeSpan.FromMilliseconds(10), 3) + { + BackoffCoefficient = 2.0, // Double the interval each time + MaxRetryInterval = TimeSpan.FromSeconds(1) + }; + + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() + .WithRetryOptions(retryOptions) + .Build(); + + // Act + Task resultTask = context.ScheduleTask("TestActivity", "1.0", options, 10, 20); + + // Fail the first two attempts + context.FailTask(0, new InvalidOperationException("First failure")); + context.FailTask(1, new InvalidOperationException("Second failure")); + + // Complete the third attempt + context.CompleteTask(2, 30); + + // Verify the timer delays + Assert.AreEqual(2, context.Delays.Count); + // First retry should be at the initial interval + Assert.AreEqual(TimeSpan.FromMilliseconds(10), context.Delays[0]); + // Second retry should be at double the interval + Assert.AreEqual(TimeSpan.FromMilliseconds(20), context.Delays[1]); + + // Verify result + int result = await resultTask; + Assert.AreEqual(30, result); + } + + [TestMethod] + public async Task ScheduleTask_WithRetryAndMaxRetryInterval_ShouldCapRetryInterval() + { + // Arrange + RetryOptions retryOptions = new RetryOptions(TimeSpan.FromMilliseconds(10), 4) + { + BackoffCoefficient = 10.0, // 10x the interval each time + MaxRetryInterval = TimeSpan.FromMilliseconds(50) // Cap at 50ms + }; + + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() + .WithRetryOptions(retryOptions) + .Build(); + + // Act + Task resultTask = context.ScheduleTask("TestActivity", "1.0", options, 10, 20); + + // Fail the first three attempts + context.FailTask(0, new Exception("Failure 1")); + context.FailTask(1, new Exception("Failure 2")); + context.FailTask(2, new Exception("Failure 3")); + + // Complete the fourth attempt + context.CompleteTask(3, 30); + + // Verify the timer delays + Assert.AreEqual(3, context.Delays.Count); + Assert.AreEqual(TimeSpan.FromMilliseconds(10), context.Delays[0]); // First retry: 10ms + Assert.AreEqual(TimeSpan.FromMilliseconds(50), context.Delays[1]); // Second retry: capped at 50ms (would be 100ms) + Assert.AreEqual(TimeSpan.FromMilliseconds(50), context.Delays[2]); // Third retry: capped at 50ms (would be 1000ms) + + int result = await resultTask; + Assert.AreEqual(30, result); + } + + [TestMethod] + public async Task ScheduleTask_WithRetryAndRetryTimeout_ShouldFailAfterTimeout() + { + // Arrange + RetryOptions retryOptions = new RetryOptions(TimeSpan.FromMilliseconds(200), 10) // Allow up to 10 retries + { + RetryTimeout = TimeSpan.FromMilliseconds(250) // But timeout after 250ms total + }; + + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() + .WithRetryOptions(retryOptions) + .Build(); + + // Act - start a task that we'll retry + Task resultTask = context.ScheduleTask("TestActivity", "1.0", options, 10, 20); + + // Simulate the first failure immediately + context.FailTask(0, new Exception("First failure")); + + // Verify we attempted a retry + Assert.AreEqual(3, context.ScheduledTasks.Count); + + // Now advance the time beyond the timeout + context.CurrentUtcDateTime = context.CurrentUtcDateTime.AddMilliseconds(300); + + // Fail the second attempt (this should now exceed the retry timeout) + context.FailTask(1, new Exception("Second failure, after timeout")); + + // Verify the number of scheduled tasks + // We expect 3 tasks because: + // 1. Original task + // 2. First retry + // 3. RetryInterceptor schedules a dummy immediate timer for backward compatibility + Assert.AreEqual(3, context.ScheduledTasks.Count); + + // Verify the task failed + try + { + await resultTask; + Assert.Fail("Task should have failed"); + } + catch (TaskFailedException ex) + { + Assert.IsInstanceOfType(ex.InnerException, typeof(Exception)); + Assert.AreEqual("Second failure, after timeout", ex.InnerException.Message); + } + } + + private class MockTaskOrchestrationContext : TaskOrchestrationContext + { + public List ScheduledTasks { get; } = new List(); + public List Delays { get; } = new List(); + + public MockTaskOrchestrationContext(OrchestrationInstance orchestrationInstance, TaskScheduler taskScheduler) + : base(orchestrationInstance, taskScheduler) + { + CurrentUtcDateTime = DateTime.UtcNow; + } + + public void CompleteTask(int taskIndex, T result) + { + string serializedResult = MessageDataConverter.SerializeInternal(result); + TaskCompletedEvent taskCompletedEvent = new TaskCompletedEvent(0, taskIndex, serializedResult); + HandleTaskCompletedEvent(taskCompletedEvent); + } + + public void CompleteTaskWithNullResult(int taskIndex) + { + TaskCompletedEvent taskCompletedEvent = new TaskCompletedEvent(0, taskIndex, null); + HandleTaskCompletedEvent(taskCompletedEvent); + } + + public void FailTask(int taskIndex, Exception exception) + { + string details = ErrorDataConverter.SerializeInternal(exception); + TaskFailedEvent taskFailedEvent = new TaskFailedEvent(0, taskIndex, exception.Message, details); + HandleTaskFailedEvent(taskFailedEvent); + } + + public override async Task CreateTimer(DateTime fireAt, T state, CancellationToken cancelToken) + { + TimeSpan delay = fireAt - CurrentUtcDateTime; + Delays.Add(delay); + + CurrentUtcDateTime = fireAt; // Advance the time + + return await Task.FromResult(state); + } + + public override async Task ScheduleTask(string name, string version, params object[] parameters) + { + ScheduledTasks.Add(new ScheduledTaskInfo + { + Name = name, + Version = version, + Parameters = parameters, + Options = null + }); + + // This just sets up the infrastructure needed for completing the task + return await base.ScheduleTask(name, version, parameters); + } + + public override async Task ScheduleTask(string name, string version, ScheduleTaskOptions options, params object[] parameters) + { + ScheduledTasks.Add(new ScheduledTaskInfo + { + Name = name, + Version = version, + Parameters = parameters, + Options = options + }); + + // This will go through TaskOrchestrationContext's implementation, which handles retries + return await base.ScheduleTask(name, version, options, parameters); + } + } + + private class ScheduledTaskInfo + { + public string Name { get; set; } + public string Version { get; set; } + public object[] Parameters { get; set; } + public ScheduleTaskOptions Options { get; set; } + } + } +} \ No newline at end of file From 746a2d493756d0134560956480da68a41334ab50 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Thu, 15 May 2025 18:04:37 -0700 Subject: [PATCH 08/21] test 'none' --- eng/templates/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/templates/test.yml b/eng/templates/test.yml index 59de51f90..7e201234d 100644 --- a/eng/templates/test.yml +++ b/eng/templates/test.yml @@ -23,7 +23,7 @@ steps: testAssemblyVer2: ${{ parameters.testAssembly }} testFiltercriteria: 'TestCategory!=DisabledInCI' vsTestVersion: 17.0 - distributionBatchType: basedOnExecutionTime + distributionBatchType: 'none' platform: 'any cpu' configuration: 'Debug' diagnosticsEnabled: True From 7371eddd34fcd32b778413437cd191aa7fe753d0 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Thu, 15 May 2025 18:33:57 -0700 Subject: [PATCH 09/21] Revert "test 'none'" This reverts commit 746a2d493756d0134560956480da68a41334ab50. --- eng/templates/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/templates/test.yml b/eng/templates/test.yml index 7e201234d..59de51f90 100644 --- a/eng/templates/test.yml +++ b/eng/templates/test.yml @@ -23,7 +23,7 @@ steps: testAssemblyVer2: ${{ parameters.testAssembly }} testFiltercriteria: 'TestCategory!=DisabledInCI' vsTestVersion: 17.0 - distributionBatchType: 'none' + distributionBatchType: basedOnExecutionTime platform: 'any cpu' configuration: 'Debug' diagnosticsEnabled: True From a74b7cc86cb7c2d9c9fc0232e94cda568cfe1cfd Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Thu, 15 May 2025 22:44:03 -0700 Subject: [PATCH 10/21] refresh ci test build --- eng/templates/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eng/templates/test.yml b/eng/templates/test.yml index 59de51f90..4f511e635 100644 --- a/eng/templates/test.yml +++ b/eng/templates/test.yml @@ -23,7 +23,8 @@ steps: testAssemblyVer2: ${{ parameters.testAssembly }} testFiltercriteria: 'TestCategory!=DisabledInCI' vsTestVersion: 17.0 - distributionBatchType: basedOnExecutionTime + runInParallel: false + dontDistribute: true platform: 'any cpu' configuration: 'Debug' diagnosticsEnabled: True From 59e07a8b96602c864bc0fe0f64302e3b37df5f5f Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Thu, 15 May 2025 23:00:01 -0700 Subject: [PATCH 11/21] Revert "refresh ci test build" This reverts commit a74b7cc86cb7c2d9c9fc0232e94cda568cfe1cfd. --- eng/templates/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/eng/templates/test.yml b/eng/templates/test.yml index 4f511e635..59de51f90 100644 --- a/eng/templates/test.yml +++ b/eng/templates/test.yml @@ -23,8 +23,7 @@ steps: testAssemblyVer2: ${{ parameters.testAssembly }} testFiltercriteria: 'TestCategory!=DisabledInCI' vsTestVersion: 17.0 - runInParallel: false - dontDistribute: true + distributionBatchType: basedOnExecutionTime platform: 'any cpu' configuration: 'Debug' diagnosticsEnabled: True From bc9eb063c257fd10902878fd1f98f64a3f889218 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Thu, 15 May 2025 22:44:03 -0700 Subject: [PATCH 12/21] refresh ci test build --- eng/templates/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eng/templates/test.yml b/eng/templates/test.yml index 59de51f90..4f511e635 100644 --- a/eng/templates/test.yml +++ b/eng/templates/test.yml @@ -23,7 +23,8 @@ steps: testAssemblyVer2: ${{ parameters.testAssembly }} testFiltercriteria: 'TestCategory!=DisabledInCI' vsTestVersion: 17.0 - distributionBatchType: basedOnExecutionTime + runInParallel: false + dontDistribute: true platform: 'any cpu' configuration: 'Debug' diagnosticsEnabled: True From 2f831f1f6d5ae66240369a84fa5bbbd5af9a1e51 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Thu, 15 May 2025 23:38:00 -0700 Subject: [PATCH 13/21] remove test --- .../ScheduleTaskOptionsTests.cs | 44 +++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/Test/DurableTask.Core.Tests/ScheduleTaskOptionsTests.cs b/Test/DurableTask.Core.Tests/ScheduleTaskOptionsTests.cs index 1c79b9dbd..5fc266e63 100644 --- a/Test/DurableTask.Core.Tests/ScheduleTaskOptionsTests.cs +++ b/Test/DurableTask.Core.Tests/ScheduleTaskOptionsTests.cs @@ -4,9 +4,9 @@ namespace DurableTask.Core.Tests { + using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; - using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] public class ScheduleTaskOptionsTests @@ -15,7 +15,7 @@ public class ScheduleTaskOptionsTests public void CreateBuilder_ShouldReturnBuilderInstance() { // Act - var builder = ScheduleTaskOptions.CreateBuilder(); + ScheduleTaskOptions.Builder builder = ScheduleTaskOptions.CreateBuilder(); // Assert Assert.IsNotNull(builder); @@ -26,7 +26,7 @@ public void CreateBuilder_ShouldReturnBuilderInstance() public void Build_ShouldCreateInstanceWithNullProperties() { // Act - var options = ScheduleTaskOptions.CreateBuilder().Build(); + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder().Build(); // Assert Assert.IsNotNull(options); @@ -38,14 +38,14 @@ public void Build_ShouldCreateInstanceWithNullProperties() public void WithTags_ShouldSetTagsProperty() { // Arrange - var tags = new Dictionary + Dictionary tags = new Dictionary { { "key1", "value1" }, { "key2", "value2" } }; // Act - var options = ScheduleTaskOptions.CreateBuilder() + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() .WithTags(tags) .Build(); @@ -60,7 +60,7 @@ public void WithTags_ShouldSetTagsProperty() public void AddTag_WithNullTags_ShouldInitializeTagsCollection() { // Act - var options = ScheduleTaskOptions.CreateBuilder() + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() .AddTag("key1", "value1") .Build(); @@ -74,13 +74,13 @@ public void AddTag_WithNullTags_ShouldInitializeTagsCollection() public void AddTag_WithExistingTags_ShouldAddToCollection() { // Arrange - var tags = new Dictionary + Dictionary tags = new Dictionary { { "key1", "value1" } }; // Act - var options = ScheduleTaskOptions.CreateBuilder() + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() .WithTags(tags) .AddTag("key2", "value2") .Build(); @@ -96,7 +96,7 @@ public void AddTag_WithExistingTags_ShouldAddToCollection() public void AddTag_OverwriteExistingKey_ShouldUpdateValue() { // Act - var options = ScheduleTaskOptions.CreateBuilder() + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() .AddTag("key1", "originalValue") .AddTag("key1", "newValue") .Build(); @@ -111,10 +111,10 @@ public void AddTag_OverwriteExistingKey_ShouldUpdateValue() public void WithRetryOptions_Instance_ShouldSetRetryOptionsProperty() { // Arrange - var retryOptions = new RetryOptions(TimeSpan.FromSeconds(5), 3); + RetryOptions retryOptions = new RetryOptions(TimeSpan.FromSeconds(5), 3); // Act - var options = ScheduleTaskOptions.CreateBuilder() + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() .WithRetryOptions(retryOptions) .Build(); @@ -129,7 +129,7 @@ public void WithRetryOptions_Instance_ShouldSetRetryOptionsProperty() public void WithRetryOptions_Parameters_ShouldCreateAndSetRetryOptions() { // Act - var options = ScheduleTaskOptions.CreateBuilder() + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() .WithRetryOptions(TimeSpan.FromSeconds(5), 3) .Build(); @@ -145,7 +145,7 @@ public void WithRetryOptions_Parameters_ShouldCreateAndSetRetryOptions() public void WithRetryOptions_WithConfigureAction_ShouldConfigureRetryOptions() { // Act - var options = ScheduleTaskOptions.CreateBuilder() + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() .WithRetryOptions(TimeSpan.FromSeconds(5), 3, retryOptions => { retryOptions.BackoffCoefficient = 2.0; @@ -165,7 +165,7 @@ public void WithRetryOptions_WithConfigureAction_ShouldConfigureRetryOptions() public void WithRetryOptions_PassNullConfigureAction_ShouldStillCreateRetryOptions() { // Act - var options = ScheduleTaskOptions.CreateBuilder() + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() .WithRetryOptions(TimeSpan.FromSeconds(5), 3, null) .Build(); @@ -179,10 +179,10 @@ public void WithRetryOptions_PassNullConfigureAction_ShouldStillCreateRetryOptio public void FluentInterface_CombiningAllMethods_ShouldBuildCorrectInstance() { // Arrange - var retryOptions = new RetryOptions(TimeSpan.FromSeconds(1), 2); + RetryOptions retryOptions = new RetryOptions(TimeSpan.FromSeconds(1), 2); // Act - var options = ScheduleTaskOptions.CreateBuilder() + ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() .AddTag("env", "test") .WithRetryOptions(retryOptions) .AddTag("priority", "high") @@ -196,15 +196,5 @@ public void FluentInterface_CombiningAllMethods_ShouldBuildCorrectInstance() Assert.AreEqual("high", options.Tags["priority"]); Assert.AreEqual(retryOptions, options.RetryOptions); } - - [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void RetryOptions_WithInvalidInterval_ShouldThrowException() - { - // Act - This should throw an ArgumentException - ScheduleTaskOptions.CreateBuilder() - .WithRetryOptions(TimeSpan.Zero, 3) - .Build(); - } } -} \ No newline at end of file +} \ No newline at end of file From 38764f965fd171f58cd8c71b18564969c80abc3e Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Fri, 16 May 2025 01:14:04 -0700 Subject: [PATCH 14/21] Revert "refresh ci test build" This reverts commit bc9eb063c257fd10902878fd1f98f64a3f889218. --- eng/templates/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/eng/templates/test.yml b/eng/templates/test.yml index 4f511e635..59de51f90 100644 --- a/eng/templates/test.yml +++ b/eng/templates/test.yml @@ -23,8 +23,7 @@ steps: testAssemblyVer2: ${{ parameters.testAssembly }} testFiltercriteria: 'TestCategory!=DisabledInCI' vsTestVersion: 17.0 - runInParallel: false - dontDistribute: true + distributionBatchType: basedOnExecutionTime platform: 'any cpu' configuration: 'Debug' diagnosticsEnabled: True From ab7e8b2fc9bf4650e4470bc5e53e72aa0caee706 Mon Sep 17 00:00:00 2001 From: wangbill Date: Fri, 16 May 2025 08:22:43 -0700 Subject: [PATCH 15/21] Update src/DurableTask.Core/ScheduleTaskOptions.cs Co-authored-by: Chris Gillum --- src/DurableTask.Core/ScheduleTaskOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DurableTask.Core/ScheduleTaskOptions.cs b/src/DurableTask.Core/ScheduleTaskOptions.cs index 8791106af..b1e556510 100644 --- a/src/DurableTask.Core/ScheduleTaskOptions.cs +++ b/src/DurableTask.Core/ScheduleTaskOptions.cs @@ -71,7 +71,7 @@ internal Builder() /// The builder instance. public Builder WithTags(IDictionary tags) { - this.options.Tags = tags; + this.options.Tags = new Dictionary(tags); return this; } From 1705e11b058eb71b0c1501b34b5c957136454cd9 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Fri, 16 May 2025 08:33:24 -0700 Subject: [PATCH 16/21] retry deep copy --- src/DurableTask.Core/RetryOptions.cs | 20 ++++++++++++++++++++ src/DurableTask.Core/ScheduleTaskOptions.cs | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/DurableTask.Core/RetryOptions.cs b/src/DurableTask.Core/RetryOptions.cs index 9f8ee2eac..6aa71fa0a 100644 --- a/src/DurableTask.Core/RetryOptions.cs +++ b/src/DurableTask.Core/RetryOptions.cs @@ -43,6 +43,26 @@ public RetryOptions(TimeSpan firstRetryInterval, int maxNumberOfAttempts) Handle = e => true; } + /// + /// Creates a new instance of RetryOptions by copying values from an existing instance + /// + /// The RetryOptions instance to copy from + /// + public RetryOptions(RetryOptions source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + FirstRetryInterval = source.FirstRetryInterval; + MaxNumberOfAttempts = source.MaxNumberOfAttempts; + MaxRetryInterval = source.MaxRetryInterval; + BackoffCoefficient = source.BackoffCoefficient; + RetryTimeout = source.RetryTimeout; + Handle = source.Handle; + } + /// /// Gets or sets the first retry interval /// diff --git a/src/DurableTask.Core/ScheduleTaskOptions.cs b/src/DurableTask.Core/ScheduleTaskOptions.cs index b1e556510..5261ec80a 100644 --- a/src/DurableTask.Core/ScheduleTaskOptions.cs +++ b/src/DurableTask.Core/ScheduleTaskOptions.cs @@ -99,7 +99,7 @@ public Builder AddTag(string key, string value) /// The builder instance. public Builder WithRetryOptions(RetryOptions retryOptions) { - this.options.RetryOptions = retryOptions; + this.options.RetryOptions = retryOptions == null ? null : new RetryOptions(retryOptions); return this; } From 62ef0bffb1092f310f119ba6c8836faa81e5e549 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Fri, 16 May 2025 08:35:04 -0700 Subject: [PATCH 17/21] revert ver --- src/DurableTask.Core/DurableTask.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DurableTask.Core/DurableTask.Core.csproj b/src/DurableTask.Core/DurableTask.Core.csproj index 6668a8a95..4bedc92a8 100644 --- a/src/DurableTask.Core/DurableTask.Core.csproj +++ b/src/DurableTask.Core/DurableTask.Core.csproj @@ -17,7 +17,7 @@ 3 - 3 + 1 0 $(MajorVersion).$(MinorVersion).$(PatchVersion) $(VersionPrefix).0 From 9e9f5a81a95c500291ade91201315552f5765855 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Thu, 15 May 2025 22:44:03 -0700 Subject: [PATCH 18/21] refresh ci test build --- eng/templates/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eng/templates/test.yml b/eng/templates/test.yml index 59de51f90..4f511e635 100644 --- a/eng/templates/test.yml +++ b/eng/templates/test.yml @@ -23,7 +23,8 @@ steps: testAssemblyVer2: ${{ parameters.testAssembly }} testFiltercriteria: 'TestCategory!=DisabledInCI' vsTestVersion: 17.0 - distributionBatchType: basedOnExecutionTime + runInParallel: false + dontDistribute: true platform: 'any cpu' configuration: 'Debug' diagnosticsEnabled: True From 8d1b873a64d733c9c271985ec4ce3d6df287bd33 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Fri, 16 May 2025 09:26:02 -0700 Subject: [PATCH 19/21] fix tests --- .../ScheduleTaskOptionsTests.cs | 5 +- .../TaskOrchestrationContextTests.cs | 234 ------------------ 2 files changed, 3 insertions(+), 236 deletions(-) diff --git a/Test/DurableTask.Core.Tests/ScheduleTaskOptionsTests.cs b/Test/DurableTask.Core.Tests/ScheduleTaskOptionsTests.cs index 5fc266e63..c61b99033 100644 --- a/Test/DurableTask.Core.Tests/ScheduleTaskOptionsTests.cs +++ b/Test/DurableTask.Core.Tests/ScheduleTaskOptionsTests.cs @@ -120,7 +120,6 @@ public void WithRetryOptions_Instance_ShouldSetRetryOptionsProperty() // Assert Assert.IsNotNull(options.RetryOptions); - Assert.AreEqual(retryOptions, options.RetryOptions); Assert.AreEqual(TimeSpan.FromSeconds(5), options.RetryOptions.FirstRetryInterval); Assert.AreEqual(3, options.RetryOptions.MaxNumberOfAttempts); } @@ -194,7 +193,9 @@ public void FluentInterface_CombiningAllMethods_ShouldBuildCorrectInstance() Assert.AreEqual(2, options.Tags.Count); Assert.AreEqual("test", options.Tags["env"]); Assert.AreEqual("high", options.Tags["priority"]); - Assert.AreEqual(retryOptions, options.RetryOptions); + Assert.IsNotNull(options.RetryOptions); + Assert.AreEqual(retryOptions.FirstRetryInterval, options.RetryOptions.FirstRetryInterval); + Assert.AreEqual(retryOptions.MaxNumberOfAttempts, options.RetryOptions.MaxNumberOfAttempts); } } } \ No newline at end of file diff --git a/Test/DurableTask.Core.Tests/TaskOrchestrationContextTests.cs b/Test/DurableTask.Core.Tests/TaskOrchestrationContextTests.cs index f49f88af5..a19d76a20 100644 --- a/Test/DurableTask.Core.Tests/TaskOrchestrationContextTests.cs +++ b/Test/DurableTask.Core.Tests/TaskOrchestrationContextTests.cs @@ -94,121 +94,6 @@ public async Task ScheduleTask_WithTags_ShouldPassTags() await resultTask; } - [TestMethod] - public async Task ScheduleTask_WithRetryOptions_ShouldRetryOnFailure() - { - // Arrange - RetryOptions retryOptions = new RetryOptions(TimeSpan.FromMilliseconds(1000), 3); - ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() - .WithRetryOptions(retryOptions) - .Build(); - - // Act - Task resultTask = context.ScheduleTask("TestActivity", "1.0", options, 10, 20); - - // Fail the first attempt - context.FailTask(0, new InvalidOperationException("First failure")); - - // Verify that a retry was scheduled - Assert.AreEqual(3, context.ScheduledTasks.Count); - - // Fail the second attempt - context.FailTask(1, new InvalidOperationException("Second failure")); - - // Verify that another retry was scheduled - Assert.AreEqual(4, context.ScheduledTasks.Count); - - // Complete the third attempt successfully - context.CompleteTask(2, 30); - - // Verify result - int result = await resultTask; - Assert.AreEqual(30, result); - - // Verify all tasks had the same name, version, and parameters - foreach (ScheduledTaskInfo task in context.ScheduledTasks) - { - Assert.AreEqual("TestActivity", task.Name); - Assert.AreEqual("1.0", task.Version); - CollectionAssert.AreEqual(new object[] { 10, 20 }, task.Parameters); - } - } - - [TestMethod] - public async Task ScheduleTask_WithRetryOptions_ShouldRespectMaxRetries() - { - // Arrange - RetryOptions retryOptions = new RetryOptions(TimeSpan.FromMilliseconds(1), 2); - ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() - .WithRetryOptions(retryOptions) - .Build(); - - // Act - Task resultTask = context.ScheduleTask("TestActivity", "1.0", options, 10, 20); - - // Fail all attempts - context.FailTask(0, new InvalidOperationException("First failure")); - context.FailTask(1, new InvalidOperationException("Second failure")); - - // Verify that no more retries are scheduled after max retries - Assert.AreEqual(3, context.ScheduledTasks.Count); - - // Verify the task failed - try - { - await resultTask; - Assert.Fail("Task should have failed"); - } - catch (TaskFailedException ex) - { - Assert.IsInstanceOfType(ex.InnerException, typeof(InvalidOperationException)); - Assert.AreEqual("Second failure", ex.InnerException.Message); - } - } - - [TestMethod] - public async Task ScheduleTask_WithRetryOptions_ShouldRespectRetryHandler() - { - // Arrange - RetryOptions retryOptions = new RetryOptions(TimeSpan.FromMilliseconds(1), 3) - { - Handle = ex => ex is ArgumentException // Only retry ArgumentException - }; - - ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() - .WithRetryOptions(retryOptions) - .Build(); - - // Act - Task resultTask = context.ScheduleTask("TestActivity", "1.0", options, 10, 20); - - // Fail with exception that should be retried - context.FailTask(0, new ArgumentException("Should retry")); - - // Verify that a retry was scheduled - Assert.AreEqual(2, context.ScheduledTasks.Count); - - // Fail with exception that should NOT be retried - context.FailTask(1, new InvalidOperationException("Should not retry")); - - // Verify that no more retries are scheduled - but expect dummy timer for backwards compatibility - Assert.AreEqual(2, context.ScheduledTasks.Count); - - // Verify the task failed with the expected exception - try - { - await resultTask; - Assert.Fail("Task should have failed"); - } - catch (TaskFailedException ex) - { - // Since we're using RetryInterceptor, the last exception is what gets propagated - // which would be the ArgumentException from the first failure - Assert.IsInstanceOfType(ex.InnerException, typeof(ArgumentException)); - Assert.AreEqual("Should retry", ex.InnerException.Message); - } - } - [TestMethod] public async Task ScheduleTask_WithDefaultReturnValue_ShouldHandleNullResult() { @@ -262,125 +147,6 @@ public async Task ScheduleTask_WithFailure_ShouldPropagateException() } } - [TestMethod] - public async Task ScheduleTask_WithRetryBackoff_ShouldApplyBackoffPolicy() - { - // Arrange - RetryOptions retryOptions = new RetryOptions(TimeSpan.FromMilliseconds(10), 3) - { - BackoffCoefficient = 2.0, // Double the interval each time - MaxRetryInterval = TimeSpan.FromSeconds(1) - }; - - ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() - .WithRetryOptions(retryOptions) - .Build(); - - // Act - Task resultTask = context.ScheduleTask("TestActivity", "1.0", options, 10, 20); - - // Fail the first two attempts - context.FailTask(0, new InvalidOperationException("First failure")); - context.FailTask(1, new InvalidOperationException("Second failure")); - - // Complete the third attempt - context.CompleteTask(2, 30); - - // Verify the timer delays - Assert.AreEqual(2, context.Delays.Count); - // First retry should be at the initial interval - Assert.AreEqual(TimeSpan.FromMilliseconds(10), context.Delays[0]); - // Second retry should be at double the interval - Assert.AreEqual(TimeSpan.FromMilliseconds(20), context.Delays[1]); - - // Verify result - int result = await resultTask; - Assert.AreEqual(30, result); - } - - [TestMethod] - public async Task ScheduleTask_WithRetryAndMaxRetryInterval_ShouldCapRetryInterval() - { - // Arrange - RetryOptions retryOptions = new RetryOptions(TimeSpan.FromMilliseconds(10), 4) - { - BackoffCoefficient = 10.0, // 10x the interval each time - MaxRetryInterval = TimeSpan.FromMilliseconds(50) // Cap at 50ms - }; - - ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() - .WithRetryOptions(retryOptions) - .Build(); - - // Act - Task resultTask = context.ScheduleTask("TestActivity", "1.0", options, 10, 20); - - // Fail the first three attempts - context.FailTask(0, new Exception("Failure 1")); - context.FailTask(1, new Exception("Failure 2")); - context.FailTask(2, new Exception("Failure 3")); - - // Complete the fourth attempt - context.CompleteTask(3, 30); - - // Verify the timer delays - Assert.AreEqual(3, context.Delays.Count); - Assert.AreEqual(TimeSpan.FromMilliseconds(10), context.Delays[0]); // First retry: 10ms - Assert.AreEqual(TimeSpan.FromMilliseconds(50), context.Delays[1]); // Second retry: capped at 50ms (would be 100ms) - Assert.AreEqual(TimeSpan.FromMilliseconds(50), context.Delays[2]); // Third retry: capped at 50ms (would be 1000ms) - - int result = await resultTask; - Assert.AreEqual(30, result); - } - - [TestMethod] - public async Task ScheduleTask_WithRetryAndRetryTimeout_ShouldFailAfterTimeout() - { - // Arrange - RetryOptions retryOptions = new RetryOptions(TimeSpan.FromMilliseconds(200), 10) // Allow up to 10 retries - { - RetryTimeout = TimeSpan.FromMilliseconds(250) // But timeout after 250ms total - }; - - ScheduleTaskOptions options = ScheduleTaskOptions.CreateBuilder() - .WithRetryOptions(retryOptions) - .Build(); - - // Act - start a task that we'll retry - Task resultTask = context.ScheduleTask("TestActivity", "1.0", options, 10, 20); - - // Simulate the first failure immediately - context.FailTask(0, new Exception("First failure")); - - // Verify we attempted a retry - Assert.AreEqual(3, context.ScheduledTasks.Count); - - // Now advance the time beyond the timeout - context.CurrentUtcDateTime = context.CurrentUtcDateTime.AddMilliseconds(300); - - // Fail the second attempt (this should now exceed the retry timeout) - context.FailTask(1, new Exception("Second failure, after timeout")); - - // Verify the number of scheduled tasks - // We expect 3 tasks because: - // 1. Original task - // 2. First retry - // 3. RetryInterceptor schedules a dummy immediate timer for backward compatibility - Assert.AreEqual(3, context.ScheduledTasks.Count); - - // Verify the task failed - try - { - await resultTask; - Assert.Fail("Task should have failed"); - } - catch (TaskFailedException ex) - { - Assert.IsInstanceOfType(ex.InnerException, typeof(Exception)); - Assert.AreEqual("Second failure, after timeout", ex.InnerException.Message); - } - } - private class MockTaskOrchestrationContext : TaskOrchestrationContext { public List ScheduledTasks { get; } = new List(); From 2a18a39669fb43b2b48fd1eda14b870e443f6d70 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Fri, 16 May 2025 10:03:48 -0700 Subject: [PATCH 20/21] Revert "refresh ci test build" This reverts commit 9e9f5a81a95c500291ade91201315552f5765855. --- eng/templates/test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/eng/templates/test.yml b/eng/templates/test.yml index 4f511e635..59de51f90 100644 --- a/eng/templates/test.yml +++ b/eng/templates/test.yml @@ -23,8 +23,7 @@ steps: testAssemblyVer2: ${{ parameters.testAssembly }} testFiltercriteria: 'TestCategory!=DisabledInCI' vsTestVersion: 17.0 - runInParallel: false - dontDistribute: true + distributionBatchType: basedOnExecutionTime platform: 'any cpu' configuration: 'Debug' diagnosticsEnabled: True From 69f96a48fb9ae2c13f6a35a8695c7f9a3ea23552 Mon Sep 17 00:00:00 2001 From: peterstone2017 <12449837+YunchuWang@users.noreply.github.com> Date: Fri, 16 May 2025 10:04:13 -0700 Subject: [PATCH 21/21] revert ver.. --- src/DurableTask.Core/DurableTask.Core.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DurableTask.Core/DurableTask.Core.csproj b/src/DurableTask.Core/DurableTask.Core.csproj index 4bedc92a8..26302990b 100644 --- a/src/DurableTask.Core/DurableTask.Core.csproj +++ b/src/DurableTask.Core/DurableTask.Core.csproj @@ -17,7 +17,7 @@ 3 - 1 + 2 0 $(MajorVersion).$(MinorVersion).$(PatchVersion) $(VersionPrefix).0