Skip to content

Commit 34b3566

Browse files
authored
Merge branch 'main' into copilot/report-date-timeoffset-usage
2 parents 3df2149 + 9af6667 commit 34b3566

32 files changed

+1894
-56
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Summary
2+
## What changed?
3+
-
4+
5+
## Why is this change needed?
6+
-
7+
8+
## Issues / work items
9+
- Resolves #
10+
- Related #
11+
12+
---
13+
14+
# Project checklist
15+
- [ ] Release notes are not required for the next release
16+
- [ ] Otherwise: Notes added to `release_notes.md`
17+
- [ ] Backport is not required
18+
- [ ] Otherwise: Backport tracked by issue/PR #issue_or_pr
19+
- [ ] All required tests have been added/updated (unit tests, E2E tests)
20+
- [ ] Breaking change?
21+
- [ ] If yes:
22+
- Impact:
23+
- Migration guidance:
24+
---
25+
26+
# AI-assisted code disclosure (required)
27+
## Was an AI tool used? (select one)
28+
- [ ] No
29+
- [ ] Yes, AI helped write parts of this PR (e.g., GitHub Copilot)
30+
- [ ] Yes, an AI agent generated most of this PR
31+
32+
If AI was used:
33+
- Tool(s):
34+
- AI-assisted areas/files:
35+
- What you changed after AI output:
36+
37+
AI verification (required if AI was used):
38+
- [ ] I understand the code and can explain it
39+
- [ ] I verified referenced APIs/types exist and are correct
40+
- [ ] I reviewed edge cases/failure paths (timeouts, retries, cancellation, exceptions)
41+
- [ ] I reviewed concurrency/async behavior
42+
- [ ] I checked for unintended breaking or behavior changes
43+
44+
---
45+
46+
# Testing
47+
## Automated tests
48+
- Result: Passed / Failed (link logs if failed)
49+
50+
## Manual validation (only if runtime/behavior changed)
51+
- Environment (OS, .NET version, components):
52+
- Steps + observed results:
53+
1.
54+
2.
55+
3.
56+
- Evidence (optional):
57+
58+
---
59+
60+
# Notes for reviewers
61+
- N/A

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
# Changelog
1+
# Changelog
2+
3+
## v1.18.1
4+
- Support dedup status when starting orchestration by wangbill ([#542](https://github.com/microsoft/durabletask-dotnet/pull/542))
5+
- Add 404 exception handling in blobpayloadstore.downloadasync by Copilot ([#534](https://github.com/microsoft/durabletask-dotnet/pull/534))
6+
- Bump analyzers version to 0.2.0 by Copilot ([#552](https://github.com/microsoft/durabletask-dotnet/pull/552))
7+
- Add integration tests for exception type handling by Copilot ([#544](https://github.com/microsoft/durabletask-dotnet/pull/544))
8+
- Add roslyn analyzer to detect calls to non-existent functions (name mismatch) by Copilot ([#530](https://github.com/microsoft/durabletask-dotnet/pull/530))
9+
- Remove preview suffix by Copilot ([#541](https://github.com/microsoft/durabletask-dotnet/pull/541))
10+
- Add xml documentation with see cref links to generated code for better ide navigation by Copilot ([#535](https://github.com/microsoft/durabletask-dotnet/pull/535))
11+
- Add entity source generation support for durable functions by Copilot ([#533](https://github.com/microsoft/durabletask-dotnet/pull/533))
212

313
## v1.18.0
414
- Add taskentity support to durabletasksourcegenerator by Copilot ([#517](https://github.com/microsoft/durabletask-dotnet/pull/517))

Microsoft.DurableTask.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScheduledTasks.Tests", "tes
9393
EndProject
9494
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LargePayloadConsoleApp", "samples\LargePayloadConsoleApp\LargePayloadConsoleApp.csproj", "{6EB9D002-62C8-D6C1-62A8-14C54CA6DBBC}"
9595
EndProject
96+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExceptionPropertiesSample", "samples\ExceptionPropertiesSample\ExceptionPropertiesSample.csproj", "{7C3ECBCE-BEFB-4982-842E-B654BB6B6285}"
97+
EndProject
9698
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureBlobPayloads", "src\Extensions\AzureBlobPayloads\AzureBlobPayloads.csproj", "{FE1DA748-D6DB-E168-BC42-6DBBCEAF229C}"
9799
EndProject
98100
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InProcessTestHost", "src\InProcessTestHost\InProcessTestHost.csproj", "{5F1E1662-D2D1-4325-BFE3-6AE23A8A4D7E}"
@@ -261,6 +263,10 @@ Global
261263
{6EB9D002-62C8-D6C1-62A8-14C54CA6DBBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
262264
{6EB9D002-62C8-D6C1-62A8-14C54CA6DBBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
263265
{6EB9D002-62C8-D6C1-62A8-14C54CA6DBBC}.Release|Any CPU.Build.0 = Release|Any CPU
266+
{7C3ECBCE-BEFB-4982-842E-B654BB6B6285}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
267+
{7C3ECBCE-BEFB-4982-842E-B654BB6B6285}.Debug|Any CPU.Build.0 = Debug|Any CPU
268+
{7C3ECBCE-BEFB-4982-842E-B654BB6B6285}.Release|Any CPU.ActiveCfg = Release|Any CPU
269+
{7C3ECBCE-BEFB-4982-842E-B654BB6B6285}.Release|Any CPU.Build.0 = Release|Any CPU
264270
{FE1DA748-D6DB-E168-BC42-6DBBCEAF229C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
265271
{FE1DA748-D6DB-E168-BC42-6DBBCEAF229C}.Debug|Any CPU.Build.0 = Debug|Any CPU
266272
{FE1DA748-D6DB-E168-BC42-6DBBCEAF229C}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -313,6 +319,7 @@ Global
313319
{5F1E1662-D2D1-4325-BFE3-6AE23A8A4D7E} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
314320
{B894780C-338F-475E-8E84-56AFA8197A06} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
315321
{6EB9D002-62C8-D6C1-62A8-14C54CA6DBBC} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
322+
{7C3ECBCE-BEFB-4982-842E-B654BB6B6285} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
316323
{FE1DA748-D6DB-E168-BC42-6DBBCEAF229C} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
317324
EndGlobalSection
318325
GlobalSection(ExtensibilityGlobals) = postSolution

eng/targets/Release.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
</PropertyGroup>
1818

1919
<PropertyGroup>
20-
<VersionPrefix>1.18.0</VersionPrefix>
20+
<VersionPrefix>1.18.1</VersionPrefix>
2121
<VersionSuffix></VersionSuffix>
2222
</PropertyGroup>
2323

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.DurableTask.Worker;
5+
6+
namespace ExceptionPropertiesSample;
7+
8+
/// <summary>
9+
/// Custom exception properties provider that extracts additional properties from exceptions
10+
/// to include in TaskFailureDetails for better diagnostics and error handling.
11+
/// </summary>
12+
public class CustomExceptionPropertiesProvider : IExceptionPropertiesProvider
13+
{
14+
/// <summary>
15+
/// Extracts custom properties from exceptions to enrich failure details.
16+
/// </summary>
17+
/// <param name="exception">The exception to extract properties from.</param>
18+
/// <returns>
19+
/// A dictionary of custom properties to include in the FailureDetails,
20+
/// or null if no properties should be added for this exception type.
21+
/// </returns>
22+
public IDictionary<string, object?>? GetExceptionProperties(Exception exception)
23+
{
24+
return exception switch
25+
{
26+
BusinessValidationException businessEx => new Dictionary<string, object?>
27+
{
28+
["ErrorCode"] = businessEx.ErrorCode,
29+
["StatusCode"] = businessEx.StatusCode,
30+
["Metadata"] = businessEx.Metadata,
31+
},
32+
ArgumentOutOfRangeException argEx => new Dictionary<string, object?>
33+
{
34+
["ParameterName"] = argEx.ParamName ?? string.Empty,
35+
["ActualValue"] = argEx.ActualValue?.ToString() ?? string.Empty,
36+
},
37+
ArgumentNullException argNullEx => new Dictionary<string, object?>
38+
{
39+
["ParameterName"] = argNullEx.ParamName ?? string.Empty,
40+
},
41+
_ => null // No custom properties for other exception types
42+
};
43+
}
44+
}
45+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace ExceptionPropertiesSample;
5+
6+
/// <summary>
7+
/// Custom business exception that includes additional properties for better error diagnostics.
8+
/// </summary>
9+
public class BusinessValidationException : Exception
10+
{
11+
public BusinessValidationException(
12+
string message,
13+
string? errorCode = null,
14+
int? statusCode = null,
15+
Dictionary<string, object?>? metadata = null)
16+
: base(message)
17+
{
18+
this.ErrorCode = errorCode;
19+
this.StatusCode = statusCode;
20+
this.Metadata = metadata ?? new Dictionary<string, object?>();
21+
}
22+
23+
/// <summary>
24+
/// Gets the error code associated with this validation failure.
25+
/// </summary>
26+
public string? ErrorCode { get; }
27+
28+
/// <summary>
29+
/// Gets the HTTP status code that should be returned for this error.
30+
/// </summary>
31+
public int? StatusCode { get; }
32+
33+
/// <summary>
34+
/// Gets additional metadata about the validation failure.
35+
/// </summary>
36+
public Dictionary<string, object?> Metadata { get; }
37+
}
38+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFrameworks>net6.0;net8.0;net10.0</TargetFrameworks>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.Extensions.Hosting" />
11+
12+
<!-- Real projects would use package references -->
13+
<!--
14+
<PackageReference Include="Microsoft.DurableTask.Client.Grpc" Version="1.5.0" />
15+
<PackageReference Include="Microsoft.DurableTask.Worker.Grpc" Version="1.5.0" />
16+
-->
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<!-- Using p2p references so we can show latest changes in samples. -->
21+
<ProjectReference Include="$(SrcRoot)Client/Grpc/Client.Grpc.csproj" />
22+
<ProjectReference Include="$(SrcRoot)Client/AzureManaged/Client.AzureManaged.csproj" />
23+
<ProjectReference Include="$(SrcRoot)Worker/Grpc/Worker.Grpc.csproj" />
24+
<ProjectReference Include="$(SrcRoot)Worker/AzureManaged/Worker.AzureManaged.csproj" />
25+
</ItemGroup>
26+
27+
</Project>
28+
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
// This sample demonstrates how to use IExceptionPropertiesProvider to enrich
5+
// TaskFailureDetails with custom exception properties for better diagnostics.
6+
7+
using ExceptionPropertiesSample;
8+
using Microsoft.Extensions.Configuration;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.DurableTask.Client;
11+
using Microsoft.DurableTask.Client.AzureManaged;
12+
using Microsoft.DurableTask.Worker;
13+
using Microsoft.DurableTask.Worker.AzureManaged;
14+
using Microsoft.Extensions.Hosting;
15+
16+
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
17+
18+
string? schedulerConnectionString = builder.Configuration.GetValue<string>("DURABLE_TASK_SCHEDULER_CONNECTION_STRING");
19+
bool useScheduler = !string.IsNullOrWhiteSpace(schedulerConnectionString);
20+
21+
// Register the durable task client
22+
if (useScheduler)
23+
{
24+
builder.Services.AddDurableTaskClient(clientBuilder => clientBuilder.UseDurableTaskScheduler(schedulerConnectionString!));
25+
}
26+
else
27+
{
28+
builder.Services.AddDurableTaskClient().UseGrpc();
29+
}
30+
31+
// Register the durable task worker with custom exception properties provider
32+
if (useScheduler)
33+
{
34+
builder.Services.AddDurableTaskWorker(workerBuilder =>
35+
{
36+
workerBuilder.AddTasks(tasks =>
37+
{
38+
tasks.AddOrchestrator<ValidationOrchestration>();
39+
tasks.AddActivity<ValidateInputActivity>();
40+
});
41+
42+
workerBuilder.UseDurableTaskScheduler(schedulerConnectionString!);
43+
});
44+
}
45+
else
46+
{
47+
builder.Services.AddDurableTaskWorker()
48+
.AddTasks(tasks =>
49+
{
50+
tasks.AddOrchestrator<ValidationOrchestration>();
51+
tasks.AddActivity<ValidateInputActivity>();
52+
})
53+
.UseGrpc();
54+
}
55+
56+
// Register the custom exception properties provider
57+
// This will automatically extract custom properties from exceptions and include them in TaskFailureDetails
58+
builder.Services.AddSingleton<IExceptionPropertiesProvider, CustomExceptionPropertiesProvider>();
59+
60+
IHost host = builder.Build();
61+
62+
// Start the worker
63+
await host.StartAsync();
64+
65+
// Get the client to start orchestrations
66+
DurableTaskClient client = host.Services.GetRequiredService<DurableTaskClient>();
67+
68+
Console.WriteLine("Exception Properties Sample");
69+
Console.WriteLine("===========================");
70+
Console.WriteLine();
71+
72+
Console.WriteLine(useScheduler
73+
? "Configured to use Durable Task Scheduler (DTS)."
74+
: "Configured to use local gRPC. (Set DURABLE_TASK_SCHEDULER_CONNECTION_STRING to use DTS.)");
75+
Console.WriteLine();
76+
77+
// Test case 1: Valid input (should succeed)
78+
Console.WriteLine("Test 1: Valid input");
79+
string instanceId1 = await client.ScheduleNewOrchestrationInstanceAsync(
80+
"ValidationOrchestration",
81+
input: "Hello World");
82+
83+
OrchestrationMetadata result1 = await client.WaitForInstanceCompletionAsync(
84+
instanceId1,
85+
getInputsAndOutputs: true);
86+
87+
if (result1.RuntimeStatus == OrchestrationRuntimeStatus.Completed)
88+
{
89+
Console.WriteLine($"✓ Orchestration completed successfully");
90+
Console.WriteLine($" Output: {result1.ReadOutputAs<string>()}");
91+
}
92+
Console.WriteLine();
93+
94+
// Test case 2: Empty input (should fail with custom properties)
95+
Console.WriteLine("Test 2: Empty input (should fail)");
96+
string instanceId2 = await client.ScheduleNewOrchestrationInstanceAsync(
97+
"ValidationOrchestration",
98+
input: string.Empty);
99+
100+
OrchestrationMetadata result2 = await client.WaitForInstanceCompletionAsync(
101+
instanceId2,
102+
getInputsAndOutputs: true);
103+
104+
if (result2.RuntimeStatus == OrchestrationRuntimeStatus.Failed && result2.FailureDetails != null)
105+
{
106+
Console.WriteLine($"✗ Orchestration failed as expected");
107+
Console.WriteLine($" Error Type: {result2.FailureDetails.ErrorType}");
108+
Console.WriteLine($" Error Message: {result2.FailureDetails.ErrorMessage}");
109+
110+
// Display custom properties that were extracted by IExceptionPropertiesProvider
111+
if (result2.FailureDetails.Properties != null && result2.FailureDetails.Properties.Count > 0)
112+
{
113+
Console.WriteLine($" Custom Properties:");
114+
foreach (var property in result2.FailureDetails.Properties)
115+
{
116+
Console.WriteLine($" - {property.Key}: {property.Value}");
117+
}
118+
}
119+
}
120+
Console.WriteLine();
121+
122+
// Test case 3: Short input (should fail with different custom properties)
123+
Console.WriteLine("Test 3: Short input (should fail)");
124+
string instanceId3 = await client.ScheduleNewOrchestrationInstanceAsync(
125+
"ValidationOrchestration",
126+
input: "Hi");
127+
128+
OrchestrationMetadata result3 = await client.WaitForInstanceCompletionAsync(
129+
instanceId3,
130+
getInputsAndOutputs: true);
131+
132+
if (result3.RuntimeStatus == OrchestrationRuntimeStatus.Failed && result3.FailureDetails != null)
133+
{
134+
Console.WriteLine($"✗ Orchestration failed as expected");
135+
Console.WriteLine($" Error Type: {result3.FailureDetails.ErrorType}");
136+
Console.WriteLine($" Error Message: {result3.FailureDetails.ErrorMessage}");
137+
138+
// Display custom properties
139+
if (result3.FailureDetails.Properties != null && result3.FailureDetails.Properties.Count > 0)
140+
{
141+
Console.WriteLine($" Custom Properties:");
142+
foreach (var property in result3.FailureDetails.Properties)
143+
{
144+
Console.WriteLine($" - {property.Key}: {property.Value}");
145+
}
146+
}
147+
}
148+
Console.WriteLine();
149+
150+
Console.WriteLine("Sample completed. Press any key to exit...");
151+
Console.ReadKey();
152+
153+
await host.StopAsync();
154+

0 commit comments

Comments
 (0)