From ac20a9230bd58bb4ac244e9b019006ef18074982 Mon Sep 17 00:00:00 2001 From: "naiyuantian@microsoft.com" Date: Mon, 20 Oct 2025 12:58:46 -0700 Subject: [PATCH 1/2] update --- .../DurableTask.ApplicationInsights.csproj | 2 +- .../DurableTask.AzureStorage.csproj | 2 +- src/DurableTask.Core/DurableTask.Core.csproj | 2 +- .../TaskOrchestrationContext.cs | 2 +- .../ExceptionHandlingIntegrationTests.cs | 103 ++++++++++++++++++ 5 files changed, 107 insertions(+), 4 deletions(-) diff --git a/src/DurableTask.ApplicationInsights/DurableTask.ApplicationInsights.csproj b/src/DurableTask.ApplicationInsights/DurableTask.ApplicationInsights.csproj index 9427a8215..1c168a38c 100644 --- a/src/DurableTask.ApplicationInsights/DurableTask.ApplicationInsights.csproj +++ b/src/DurableTask.ApplicationInsights/DurableTask.ApplicationInsights.csproj @@ -12,7 +12,7 @@ 0 7 - 0 + 1 $(MajorVersion).$(MinorVersion).$(PatchVersion) $(VersionPrefix).0 diff --git a/src/DurableTask.AzureStorage/DurableTask.AzureStorage.csproj b/src/DurableTask.AzureStorage/DurableTask.AzureStorage.csproj index a72a2bc52..09d6cc84f 100644 --- a/src/DurableTask.AzureStorage/DurableTask.AzureStorage.csproj +++ b/src/DurableTask.AzureStorage/DurableTask.AzureStorage.csproj @@ -22,7 +22,7 @@ 2 6 - 0 + 1 $(MajorVersion).$(MinorVersion).$(PatchVersion) $(VersionPrefix).0 diff --git a/src/DurableTask.Core/DurableTask.Core.csproj b/src/DurableTask.Core/DurableTask.Core.csproj index 848c454a8..b7acf7eec 100644 --- a/src/DurableTask.Core/DurableTask.Core.csproj +++ b/src/DurableTask.Core/DurableTask.Core.csproj @@ -18,7 +18,7 @@ 3 5 - 0 + 1 $(MajorVersion).$(MinorVersion).$(PatchVersion) $(VersionPrefix).0 diff --git a/src/DurableTask.Core/TaskOrchestrationContext.cs b/src/DurableTask.Core/TaskOrchestrationContext.cs index 87fc435b3..0210a10f6 100644 --- a/src/DurableTask.Core/TaskOrchestrationContext.cs +++ b/src/DurableTask.Core/TaskOrchestrationContext.cs @@ -686,7 +686,7 @@ public void FailOrchestration(Exception failure, OrchestrationRuntimeState runti { if (this.ErrorPropagationMode == ErrorPropagationMode.UseFailureDetails) { - failureDetails = new FailureDetails(failure); + failureDetails = new FailureDetails(failure, this.ExceptionPropertiesProvider.ExtractProperties(failure)); } else { diff --git a/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs b/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs index a480279d5..1ec37b6d9 100644 --- a/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs +++ b/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs @@ -335,6 +335,70 @@ await this.worker } } + [TestMethod] + // Test that when a provider is set, exception thrown by orchestration properties will be included if type is matched. + public async Task ExceptionPropertiesProvider_SimpleThrowExceptionOrchestration() + { + this.worker.ExceptionPropertiesProvider = new TestExceptionPropertiesProvider(); + this.worker.ErrorPropagationMode = ErrorPropagationMode.UseFailureDetails; + + try + { + await this.worker + .AddTaskOrchestrations(typeof(SimpleThrowExceptionOrchestration)) + .StartAsync(); + + var instance = await this.client.CreateOrchestrationInstanceAsync(typeof(SimpleThrowExceptionOrchestration), "test-input"); + var result = await this.client.WaitForOrchestrationAsync(instance, DefaultTimeout); + + // Check that custom properties were extracted + Assert.AreEqual(OrchestrationStatus.Failed, result.OrchestrationStatus); + Assert.IsNotNull(result.FailureDetails); + Assert.IsNotNull(result.FailureDetails.Properties); + + // Check the properties match the ArgumentOutOfRangeException. + Assert.AreEqual("count", result.FailureDetails.Properties["Name"]); + Assert.AreEqual("100", result.FailureDetails.Properties["Value"]); + } + finally + { + await this.worker.StopAsync(); + } + } + + [TestMethod] + // Test that when a provider is set, exception properties are included in failure details with propogation. + public async Task ExceptionPropertiesProvider_SubOrchestrationThrowExceptionOrchestration() + { + this.worker.ExceptionPropertiesProvider = new TestExceptionPropertiesProvider(); + this.worker.ErrorPropagationMode = ErrorPropagationMode.UseFailureDetails; + + try + { + await this.worker + .AddTaskOrchestrations(typeof(SubOrchestrationThrowExceptionOrchestration)) + .AddTaskOrchestrations(typeof(ThrowArgumentOutofRangeExceptionASubOrchestration)) + .AddTaskActivities(typeof(ThrowArgumentOutofRangeExceptionActivity)) + .StartAsync(); + + var instance = await this.client.CreateOrchestrationInstanceAsync(typeof(SubOrchestrationThrowExceptionOrchestration), "test-input"); + var result = await this.client.WaitForOrchestrationAsync(instance, DefaultTimeout); + + // Check that custom properties were extracted + Assert.AreEqual(OrchestrationStatus.Failed, result.OrchestrationStatus); + Assert.IsNotNull(result.FailureDetails); + Assert.IsNotNull(result.FailureDetails.Properties); + + // Check the properties match the ArgumentOutOfRangeException. + Assert.AreEqual("count", result.FailureDetails.Properties["Name"]); + Assert.AreEqual("100", result.FailureDetails.Properties["Value"]); + } + finally + { + await this.worker.StopAsync(); + } + } + class ThrowCustomExceptionOrchestration : TaskOrchestration { public override async Task RunTask(OrchestrationContext context, string input) @@ -352,6 +416,40 @@ protected override string Execute(TaskContext context, string input) } } + class SimpleThrowExceptionOrchestration : TaskOrchestration + { + public override Task RunTask(OrchestrationContext context, string input) + { + throw new ArgumentOutOfRangeException("count", 100, "Count is not valid."); + } + } + + class SubOrchestrationThrowExceptionOrchestration : TaskOrchestration + { + public override async Task RunTask(OrchestrationContext context, string input) + { + await context.CreateSubOrchestrationInstance(typeof(ThrowArgumentOutofRangeExceptionASubOrchestration), input); + return "This should never be reached"; + } + } + + class ThrowArgumentOutofRangeExceptionASubOrchestration : TaskOrchestration + { + public override async Task RunTask(OrchestrationContext context, string input) + { + await context.ScheduleTask(typeof(ThrowArgumentOutofRangeExceptionActivity), input); + return "This should never be reached"; + } + } + + class ThrowArgumentOutofRangeExceptionActivity : TaskActivity + { + protected override string Execute(TaskContext context, string input) + { + throw new ArgumentOutOfRangeException("count", 100, "Count is not valid."); + } + } + class ThrowInvalidOperationExceptionOrchestration : TaskOrchestration { public override async Task RunTask(OrchestrationContext context, string input) @@ -409,6 +507,11 @@ class TestExceptionPropertiesProvider : IExceptionPropertiesProvider { return exception switch { + ArgumentOutOfRangeException e => new Dictionary + { + ["Name"] = e.ParamName ?? string.Empty, + ["Value"] = e.ActualValue?.ToString() ?? string.Empty, + }, CustomBusinessException businessEx => new Dictionary { ["ExceptionTypeName"] = nameof(CustomBusinessException), From 1138c84e6a64a34a0a3ee676eff8c1b8172e4a9b Mon Sep 17 00:00:00 2001 From: "naiyuantian@microsoft.com" Date: Mon, 20 Oct 2025 13:25:17 -0700 Subject: [PATCH 2/2] fix comment --- .../ExceptionHandlingIntegrationTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs b/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs index 1ec37b6d9..8480418eb 100644 --- a/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs +++ b/test/DurableTask.Core.Tests/ExceptionHandlingIntegrationTests.cs @@ -336,7 +336,8 @@ await this.worker } [TestMethod] - // Test that when a provider is set, exception thrown by orchestration properties will be included if type is matched. + // Test that when a provider is set, properties of exception thrown by orchestration directly will be included + // if excception type is matched. public async Task ExceptionPropertiesProvider_SimpleThrowExceptionOrchestration() { this.worker.ExceptionPropertiesProvider = new TestExceptionPropertiesProvider();