Skip to content

Commit 28369d2

Browse files
authored
Merge branch 'dev' into nytian/sc-pkg-as
2 parents 6d264be + ad1a8ee commit 28369d2

File tree

13 files changed

+252
-25
lines changed

13 files changed

+252
-25
lines changed

azure-pipelines-release-nightly.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,22 @@ steps:
164164

165165
# zip .NET in-proc perf tests
166166
- task: DotNetCoreCLI@2
167-
displayName: 'Zip .NET in-proc perf tests'
167+
displayName: 'Zip .NET in-proc perf tests (net8.0)'
168168
inputs:
169169
command: 'publish'
170170
publishWebProjects: false
171171
projects: '$(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/**/*.csproj'
172-
arguments: '-o $(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/Output'
172+
arguments: '-f net8.0 -o $(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/Output/net8.0'
173+
zipAfterPublish: true
174+
modifyOutputPath: true
175+
176+
- task: DotNetCoreCLI@2
177+
displayName: 'Zip .NET in-proc perf tests (net10.0)'
178+
inputs:
179+
command: 'publish'
180+
publishWebProjects: false
181+
projects: '$(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/**/*.csproj'
182+
arguments: '-f net10.0 -o $(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/Output/net10.0'
173183
zipAfterPublish: true
174184
modifyOutputPath: true
175185

azure-pipelines-release.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,22 @@ steps:
161161

162162
# zip .NET in-proc perf tests
163163
- task: DotNetCoreCLI@2
164-
displayName: 'Zip .NET in-proc perf tests'
164+
displayName: 'Zip .NET in-proc perf tests (net8.0)'
165165
inputs:
166166
command: 'publish'
167167
publishWebProjects: false
168168
projects: '$(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/**/*.csproj'
169-
arguments: '-o $(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/Output'
169+
arguments: '-f net8.0 -o $(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/Output/net8.0'
170+
zipAfterPublish: true
171+
modifyOutputPath: true
172+
173+
- task: DotNetCoreCLI@2
174+
displayName: 'Zip .NET in-proc perf tests (net10.0)'
175+
inputs:
176+
command: 'publish'
177+
publishWebProjects: false
178+
projects: '$(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/**/*.csproj'
179+
arguments: '-f net10.0 -o $(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/Output/net10.0'
170180
zipAfterPublish: true
171181
modifyOutputPath: true
172182

eng/templates/build.yml

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ jobs:
3636
packageType: 'sdk'
3737
version: '8.0.x'
3838

39+
- task: UseDotNet@2
40+
displayName: 'Use the .NET 10 SDK'
41+
inputs:
42+
packageType: 'sdk'
43+
version: '10.0.x'
44+
3945
- task: JavaToolInstaller@0
4046
inputs:
4147
versionSpec: '17'
@@ -127,12 +133,22 @@ jobs:
127133

128134
# zip .NET in-proc perf tests
129135
- task: DotNetCoreCLI@2
130-
displayName: 'Zip .NET in-proc perf tests'
136+
displayName: 'Zip .NET in-proc perf tests (net8.0)'
137+
inputs:
138+
command: 'publish'
139+
publishWebProjects: false
140+
projects: '$(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/**/*.csproj'
141+
arguments: '-f net8.0 -o $(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/Output/net8.0'
142+
zipAfterPublish: true
143+
modifyOutputPath: true
144+
145+
- task: DotNetCoreCLI@2
146+
displayName: 'Zip .NET in-proc perf tests (net10.0)'
131147
inputs:
132148
command: 'publish'
133149
publishWebProjects: false
134150
projects: '$(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/**/*.csproj'
135-
arguments: '-o $(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/Output'
151+
arguments: '-f net10.0 -o $(System.DefaultWorkingDirectory)/test/PerfTests/DFPerfTests/Output/net10.0'
136152
zipAfterPublish: true
137153
modifyOutputPath: true
138154

global.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"sdk": {
3-
"version": "10.0.102",
3+
"version": "10.0.103",
44
"rollForward": "latestFeature"
55
},
66
"msbuild-sdks": {

src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ namespace Microsoft.Azure.WebJobs.Extensions.DurableTask
2020
{
2121
internal class OutOfProcMiddleware
2222
{
23+
private const string NoProcessAssociatedMessage = "No process is associated";
24+
2325
private readonly DurableTaskExtension extension;
2426

2527
public OutOfProcMiddleware(DurableTaskExtension extension)
@@ -179,12 +181,8 @@ await this.LifeCycleNotificationHelper.OrchestratorStartingAsync(
179181
// - a timeout
180182
// - an out of memory exception
181183
// - a worker process exit
182-
if (functionResult.Exception is Host.FunctionTimeoutException
183-
|| functionResult.Exception?.InnerException is SessionAbortedException // see RemoteOrchestrationContext.TrySetResultInternal for details on OOM-handling
184-
|| (functionResult.Exception?.InnerException?.GetType().ToString().Contains("WorkerProcessExitException") ?? false))
184+
if (IsPlatformLevelException(functionResult.Exception))
185185
{
186-
// TODO: the `WorkerProcessExitException` type is not exposed in our dependencies, it's part of WebJobs.Host.Script.
187-
// Should we add that dependency or should it be exposed in WebJobs.Host?
188186
throw functionResult.Exception;
189187
}
190188
}
@@ -460,7 +458,7 @@ void SetErrorResult(FailureDetails failureDetails)
460458
errorType: "FunctionInvocationFailed",
461459
errorMessage: $"Invocation of function '{functionName}' failed with an exception.",
462460
stackTrace: null,
463-
innerFailure: new FailureDetails(functionResult.Exception),
461+
innerFailure: functionResult.Exception != null ? new FailureDetails(functionResult.Exception) : null,
464462
isNonRetriable: true));
465463
}
466464

@@ -639,6 +637,21 @@ public async Task CallActivityAsync(DispatchMiddlewareContext dispatchContext, F
639637
dispatchContext.SetProperty(activityResult);
640638
}
641639

640+
/// <summary>
641+
/// Checks whether the given exception represents a platform-level error that should
642+
/// abort the current dispatch and trigger a durable retry.
643+
/// </summary>
644+
private static bool IsPlatformLevelException(Exception? exception)
645+
{
646+
// TODO: the `WorkerProcessExitException` type is not exposed in our dependencies, it's part of WebJobs.Host.Script.
647+
// Should we add that dependency or should it be exposed in WebJobs.Host?
648+
return exception is Host.FunctionTimeoutException
649+
|| exception?.InnerException is SessionAbortedException // see RemoteOrchestrationContext.TrySetResultInternal for details on OOM-handling
650+
|| (exception?.InnerException?.GetType().ToString().Contains("WorkerProcessExitException", StringComparison.Ordinal) ?? false)
651+
|| (exception?.InnerException is InvalidOperationException ioe
652+
&& ioe.Message.Contains(NoProcessAssociatedMessage, StringComparison.Ordinal));
653+
}
654+
642655
private static FailureDetails GetFailureDetails(Exception e, out bool fromSerializedException)
643656
{
644657
fromSerializedException = false;

src/WebJobs.Extensions.DurableTask/WebJobs.Extensions.DurableTask.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
<AssemblyName>Microsoft.Azure.WebJobs.Extensions.DurableTask</AssemblyName>
66
<RootNamespace>Microsoft.Azure.WebJobs.Extensions.DurableTask</RootNamespace>
77
<MajorVersion>3</MajorVersion>
8-
<MinorVersion>10</MinorVersion>
9-
<PatchVersion>1</PatchVersion>
8+
<MinorVersion>11</MinorVersion>
9+
<PatchVersion>0</PatchVersion>
1010
<VersionPrefix>$(MajorVersion).$(MinorVersion).$(PatchVersion)</VersionPrefix>
1111
<FileVersion>$(MajorVersion).$(MinorVersion).$(PatchVersion)</FileVersion>
1212
<AssemblyVersion>$(MajorVersion).0.0.0</AssemblyVersion>

src/Worker.Extensions.DurableTask/AssemblyInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
using Microsoft.Azure.Functions.Worker.Extensions.Abstractions;
66

77
// TODO: Find a way to generate this dynamically at build-time
8-
[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.DurableTask", "3.10.1")]
8+
[assembly: ExtensionInformation("Microsoft.Azure.WebJobs.Extensions.DurableTask", "3.11.0")]
99
[assembly: InternalsVisibleTo("Worker.Extensions.DurableTask.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100cd1dabd5a893b40e75dc901fe7293db4a3caf9cd4d3e3ed6178d49cd476969abe74a9e0b7f4a0bb15edca48758155d35a4f05e6e852fff1b319d103b39ba04acbadd278c2753627c95e1f6f6582425374b92f51cca3deb0d2aab9de3ecda7753900a31f70a236f163006beefffe282888f85e3c76d1205ec7dfef7fa472a17b1")]

src/Worker.Extensions.DurableTask/Worker.Extensions.DurableTask.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<AssemblyOriginatorKeyFile>..\..\sign.snk</AssemblyOriginatorKeyFile>
3030

3131
<!-- Version information -->
32-
<VersionPrefix>1.14.1</VersionPrefix>
32+
<VersionPrefix>1.15.0</VersionPrefix>
3333
<VersionSuffix></VersionSuffix>
3434
<AssemblyVersion>$(VersionPrefix).0</AssemblyVersion>
3535
<!-- FileVersionRevision is expected to be set by the CI. -->
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Reflection;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using DurableTask.Core;
10+
using DurableTask.Core.Entities;
11+
using DurableTask.Core.Entities.OperationFormat;
12+
using DurableTask.Core.Exceptions;
13+
using DurableTask.Core.History;
14+
using DurableTask.Core.Middleware;
15+
using Microsoft.Azure.WebJobs.Host.Executors;
16+
using Microsoft.Extensions.Logging;
17+
using Microsoft.Extensions.Logging.Abstractions;
18+
using Microsoft.Extensions.Options;
19+
using Moq;
20+
using Xunit;
21+
22+
namespace Microsoft.Azure.WebJobs.Extensions.DurableTask.Tests
23+
{
24+
public class OutOfProcMiddlewareTests
25+
{
26+
[Fact]
27+
[Trait("Category", PlatformSpecificHelpers.TestCategory)]
28+
public async Task CallOrchestratorAsync_DifferentInvalidOperationException_DoesNotThrowSessionAbortedException()
29+
{
30+
// Arrange: a different InvalidOperationException message should NOT trigger the retry path
31+
var innerException = new InvalidOperationException("The internal function invoker returned a task that does not support return values!");
32+
var outerException = new Exception("Function invocation failed.", innerException);
33+
34+
var (middleware, dispatchContext) = this.SetupOrchestratorTest(outerException);
35+
36+
// Act: should NOT throw SessionAbortedException — instead the orchestration should be marked as failed
37+
await middleware.CallOrchestratorAsync(dispatchContext, () => Task.CompletedTask);
38+
39+
// Assert: the middleware should have set a failure result on the dispatch context
40+
var result = dispatchContext.GetProperty<OrchestratorExecutionResult>();
41+
Assert.NotNull(result);
42+
}
43+
44+
[Theory]
45+
[Trait("Category", PlatformSpecificHelpers.TestCategory)]
46+
[MemberData(nameof(PlatformLevelExceptions))]
47+
public async Task CallOrchestratorAsync_PlatformLevelException_ThrowsSessionAbortedException(Exception exception)
48+
{
49+
var (middleware, dispatchContext) = this.SetupOrchestratorTest(exception);
50+
51+
await Assert.ThrowsAsync<SessionAbortedException>(
52+
() => middleware.CallOrchestratorAsync(dispatchContext, () => Task.CompletedTask));
53+
}
54+
55+
public static IEnumerable<object[]> PlatformLevelExceptions()
56+
{
57+
// FunctionTimeoutException (top-level)
58+
yield return new object[] { new Host.FunctionTimeoutException("Function timed out.") };
59+
60+
// SessionAbortedException as InnerException (e.g. out-of-memory handling)
61+
yield return new object[] { new Exception("Function invocation failed.", new SessionAbortedException("Out of memory")) };
62+
63+
// WorkerProcessExitException as InnerException (matched by type name)
64+
yield return new object[] { new Exception("Function invocation failed.", new WorkerProcessExitExceptionStub("Worker process exited.")) };
65+
66+
// InvalidOperationException with "No process is associated" as InnerException
67+
yield return new object[] { new Exception("Function invocation failed.", new InvalidOperationException("No process is associated with this object.")) };
68+
}
69+
70+
private (OutOfProcMiddleware middleware, DispatchMiddlewareContext context) SetupOrchestratorTest(Exception executorException)
71+
{
72+
var (middleware, dispatchContext) = this.CreateMiddleware(executorException, "TestOrchestrator", FunctionType.Orchestrator);
73+
74+
var orchestrationState = new OrchestrationRuntimeState(
75+
new List<HistoryEvent>
76+
{
77+
new ExecutionStartedEvent(-1, null) { Name = "TestOrchestrator" },
78+
});
79+
80+
dispatchContext.SetProperty(orchestrationState);
81+
dispatchContext.SetProperty(new OrchestrationInstance { InstanceId = "test-instance-id" });
82+
83+
return (middleware, dispatchContext);
84+
}
85+
86+
private (OutOfProcMiddleware middleware, DispatchMiddlewareContext context) CreateMiddleware(
87+
Exception executorException, string functionName, FunctionType functionType)
88+
{
89+
var extension = CreateDurableTaskExtension();
90+
91+
var mockExecutor = new Mock<ITriggeredFunctionExecutor>();
92+
mockExecutor
93+
.Setup(e => e.TryExecuteAsync(It.IsAny<TriggeredFunctionData>(), It.IsAny<CancellationToken>()))
94+
.ReturnsAsync(new FunctionResult(false, executorException));
95+
96+
var name = new FunctionName(functionName);
97+
98+
switch (functionType)
99+
{
100+
case FunctionType.Activity:
101+
extension.RegisterActivity(name, mockExecutor.Object);
102+
break;
103+
case FunctionType.Entity:
104+
extension.RegisterEntity(name, new RegisteredFunctionInfo(mockExecutor.Object, isOutOfProc: true));
105+
break;
106+
default:
107+
extension.RegisterOrchestrator(name, new RegisteredFunctionInfo(mockExecutor.Object, isOutOfProc: true));
108+
break;
109+
}
110+
111+
var dispatchContext = new DispatchMiddlewareContext();
112+
113+
// Orchestrators and entities require WorkItemMetadata; activities do not.
114+
if (functionType != FunctionType.Activity)
115+
{
116+
dispatchContext.SetProperty(CreateWorkItemMetadata(isExtendedSession: false, includeState: false));
117+
}
118+
119+
return (new OutOfProcMiddleware(extension), dispatchContext);
120+
}
121+
122+
private static DurableTaskExtension CreateDurableTaskExtension()
123+
{
124+
var options = new DurableTaskOptions
125+
{
126+
HubName = "TestHub",
127+
WebhookUriProviderOverride = () => new Uri("https://localhost"),
128+
};
129+
130+
return new DurableTaskExtension(
131+
new OptionsWrapper<DurableTaskOptions>(options),
132+
NullLoggerFactory.Instance,
133+
TestHelpers.GetTestNameResolver(),
134+
new[]
135+
{
136+
new AzureStorageDurabilityProviderFactory(
137+
new OptionsWrapper<DurableTaskOptions>(options),
138+
new TestStorageServiceClientProviderFactory(),
139+
TestHelpers.GetTestNameResolver(),
140+
NullLoggerFactory.Instance,
141+
TestHelpers.GetMockPlatformInformationService()),
142+
},
143+
new TestHostShutdownNotificationService(),
144+
new DurableHttpMessageHandlerFactory(),
145+
platformInformationService: TestHelpers.GetMockPlatformInformationService());
146+
}
147+
148+
private static WorkItemMetadata CreateWorkItemMetadata(bool isExtendedSession, bool includeState)
149+
{
150+
// WorkItemMetadata has an internal constructor, so we use reflection to create it.
151+
var ctor = typeof(WorkItemMetadata).GetConstructor(
152+
BindingFlags.Instance | BindingFlags.NonPublic,
153+
binder: null,
154+
new[] { typeof(bool), typeof(bool) },
155+
modifiers: null);
156+
Assert.NotNull(ctor);
157+
return (WorkItemMetadata)ctor.Invoke(new object[] { isExtendedSession, includeState });
158+
}
159+
160+
/// <summary>
161+
/// Stub exception whose type name contains "WorkerProcessExitException" to match the
162+
/// string-based check in <see cref="OutOfProcMiddleware"/>. The real
163+
/// <c>WorkerProcessExitException</c> lives in <c>Microsoft.Azure.WebJobs.Script</c>
164+
/// (the Functions host runtime), which is too heavy to reference as a test dependency.
165+
/// </summary>
166+
private class WorkerProcessExitExceptionStub : Exception
167+
{
168+
public WorkerProcessExitExceptionStub(string message)
169+
: base(message)
170+
{
171+
}
172+
}
173+
}
174+
}

test/e2e/Apps/BasicJava/extensions.csproj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
<DefaultItemExcludes>**</DefaultItemExcludes>
77
</PropertyGroup>
88
<Target Name="PreBuild" BeforeTargets="Build;BeforeResolveReferences">
9-
<!-- Build and pack the source project -->
9+
<!-- Build and pack the source project. Pin to net8.0 to avoid MSB3277 assembly conflicts
10+
when the source project multi-targets net8.0;net10.0 -->
1011
<MSBuild Projects="$(MSBuildProjectDirectory)/../../../../src/WebJobs.Extensions.DurableTask/WebJobs.Extensions.DurableTask.csproj"
1112
Targets="Build;Pack"
12-
Properties="Configuration=$(Configuration);PackageOutputPath=$(MSBuildProjectDirectory)/packages" />
13+
Properties="Configuration=$(Configuration);PackageOutputPath=$(MSBuildProjectDirectory)/packages;TargetFramework=net8.0" />
1314
</Target>
1415
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
1516
<Move SourceFiles="$(OutDir)/extensions.deps.json" DestinationFiles="$(OutDir)/function.deps.json" />

0 commit comments

Comments
 (0)