Skip to content

Commit 60319b3

Browse files
sophiatevSophia Tevosyan
andauthored
Add another platform exception type to OutOfProcMiddleware (#3383)
* tested and this fix is working * added the check to entities, added tests for entities and activities * fixing stylistic warnings * updated the host dependency to retrieve the new exception type * added comment explanations * added missing traits to tests * fixing the style warning in the test file --------- Co-authored-by: Sophia Tevosyan <stevosyan@microsoft.com>
1 parent edb47ef commit 60319b3

File tree

3 files changed

+101
-38
lines changed

3 files changed

+101
-38
lines changed

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Core" Version="2.2.0" />
2323
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Abstractions" Version="1.3.0" />
2424
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs" Version="6.7.0" />
25-
<PackageVersion Include="Microsoft.Azure.WebJobs" Version="3.0.39" />
25+
<PackageVersion Include="Microsoft.Azure.WebJobs" Version="3.0.45" />
2626
<PackageVersion Include="Microsoft.Azure.WebJobs.Extensions.DurableTask.Analyzers" Version="0.5.0" />
2727
<PackageVersion Include="Microsoft.Azure.WebJobs.Extensions.Rpc" Version="3.0.39" />
2828
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.1" />

src/WebJobs.Extensions.DurableTask/OutOfProcMiddleware.cs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See LICENSE in the project root for license information.
33
#nullable enable
4-
using System;
5-
using System.Collections.Generic;
6-
using System.Diagnostics.CodeAnalysis;
7-
using System.Linq;
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 Google.Protobuf;
16-
using Microsoft.Azure.WebJobs.Host.Executors;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Linq;
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 Google.Protobuf;
16+
using Microsoft.Azure.WebJobs.Host.Executors;
1717
using P = Microsoft.DurableTask.Protobuf;
1818

1919
namespace Microsoft.Azure.WebJobs.Extensions.DurableTask
@@ -405,7 +405,14 @@ void SetErrorResult(FailureDetails failureDetails)
405405
cancellationToken: this.HostLifetimeService.OnStopping);
406406

407407
if (!functionResult.Succeeded)
408-
{
408+
{
409+
// This exception is thrown when another Function on the worker exceeded the Function timeout.
410+
// In this case we want to make sure to retry this entity's execution rather than marking it as failed.
411+
if (functionResult.Exception is Host.FunctionTimeoutAbortException)
412+
{
413+
throw functionResult.Exception;
414+
}
415+
409416
// Shutdown can surface as a completed invocation in a failed state.
410417
// Re-throw so we can abort this invocation.
411418
this.HostLifetimeService.OnStopping.ThrowIfCancellationRequested();
@@ -554,7 +561,14 @@ public async Task CallActivityAsync(DispatchMiddlewareContext dispatchContext, F
554561
triggerInput,
555562
cancellationToken: this.HostLifetimeService.OnStopping);
556563
if (!result.Succeeded)
557-
{
564+
{
565+
// This exception is thrown when another Function on the worker exceeded the Function timeout.
566+
// In this case we want to make sure to retry this Activity's execution rather than marking it as failed.
567+
if (result.Exception is Host.FunctionTimeoutAbortException)
568+
{
569+
throw result.Exception;
570+
}
571+
558572
// Shutdown can surface as a completed invocation in a failed state.
559573
// Re-throw so we can abort this invocation.
560574
this.HostLifetimeService.OnStopping.ThrowIfCancellationRequested();
@@ -646,6 +660,7 @@ private static bool IsPlatformLevelException(Exception? exception)
646660
// TODO: the `WorkerProcessExitException` type is not exposed in our dependencies, it's part of WebJobs.Host.Script.
647661
// Should we add that dependency or should it be exposed in WebJobs.Host?
648662
return exception is Host.FunctionTimeoutException
663+
|| exception is Host.FunctionTimeoutAbortException
649664
|| exception?.InnerException is SessionAbortedException // see RemoteOrchestrationContext.TrySetResultInternal for details on OOM-handling
650665
|| (exception?.InnerException?.GetType().ToString().Contains("WorkerProcessExitException", StringComparison.Ordinal) ?? false)
651666
|| (exception?.InnerException is InvalidOperationException ioe

test/FunctionsV2/OutOfProcMiddlewareTests.cs

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@
77
using System.Threading;
88
using System.Threading.Tasks;
99
using DurableTask.Core;
10-
using DurableTask.Core.Entities;
1110
using DurableTask.Core.Entities.OperationFormat;
1211
using DurableTask.Core.Exceptions;
1312
using DurableTask.Core.History;
1413
using DurableTask.Core.Middleware;
14+
using Microsoft.Azure.WebJobs.Host;
1515
using Microsoft.Azure.WebJobs.Host.Executors;
16-
using Microsoft.Extensions.Logging;
1716
using Microsoft.Extensions.Logging.Abstractions;
1817
using Microsoft.Extensions.Options;
1918
using Moq;
@@ -31,13 +30,13 @@ public async Task CallOrchestratorAsync_DifferentInvalidOperationException_DoesN
3130
var innerException = new InvalidOperationException("The internal function invoker returned a task that does not support return values!");
3231
var outerException = new Exception("Function invocation failed.", innerException);
3332

34-
var (middleware, dispatchContext) = this.SetupOrchestratorTest(outerException);
33+
(OutOfProcMiddleware middleware, DispatchMiddlewareContext dispatchContext) = this.SetupOrchestratorTest(outerException);
3534

3635
// Act: should NOT throw SessionAbortedException — instead the orchestration should be marked as failed
3736
await middleware.CallOrchestratorAsync(dispatchContext, () => Task.CompletedTask);
3837

3938
// Assert: the middleware should have set a failure result on the dispatch context
40-
var result = dispatchContext.GetProperty<OrchestratorExecutionResult>();
39+
OrchestratorExecutionResult result = dispatchContext.GetProperty<OrchestratorExecutionResult>();
4140
Assert.NotNull(result);
4241
}
4342

@@ -46,16 +45,43 @@ public async Task CallOrchestratorAsync_DifferentInvalidOperationException_DoesN
4645
[MemberData(nameof(PlatformLevelExceptions))]
4746
public async Task CallOrchestratorAsync_PlatformLevelException_ThrowsSessionAbortedException(Exception exception)
4847
{
49-
var (middleware, dispatchContext) = this.SetupOrchestratorTest(exception);
48+
(OutOfProcMiddleware middleware, DispatchMiddlewareContext dispatchContext) = this.SetupOrchestratorTest(exception);
5049

5150
await Assert.ThrowsAsync<SessionAbortedException>(
5251
() => middleware.CallOrchestratorAsync(dispatchContext, () => Task.CompletedTask));
52+
}
53+
54+
[Fact]
55+
[Trait("Category", PlatformSpecificHelpers.TestCategory)]
56+
public async Task CallEntityAsync_FunctionTimeoutAbortException_ThrowsSessionAbortedException()
57+
{
58+
var exception = new FunctionTimeoutAbortException("Activity A timed out! Worker channel closing");
59+
60+
(OutOfProcMiddleware middleware, DispatchMiddlewareContext dispatchContext) = this.SetupEntityTest(exception);
61+
62+
await Assert.ThrowsAsync<SessionAbortedException>(
63+
() => middleware.CallEntityAsync(dispatchContext, () => Task.CompletedTask));
64+
}
65+
66+
[Fact]
67+
[Trait("Category", PlatformSpecificHelpers.TestCategory)]
68+
public async Task CallActivityAsync_FunctionTimeoutAbortException_ThrowsSessionAbortedException()
69+
{
70+
var exception = new FunctionTimeoutAbortException("Activity A timed out! Worker channel closing");
71+
72+
(OutOfProcMiddleware middleware, DispatchMiddlewareContext dispatchContext) = this.SetupActivityTest(exception);
73+
74+
await Assert.ThrowsAsync<SessionAbortedException>(
75+
() => middleware.CallActivityAsync(dispatchContext, () => Task.CompletedTask));
5376
}
5477

5578
public static IEnumerable<object[]> PlatformLevelExceptions()
5679
{
5780
// FunctionTimeoutException (top-level)
58-
yield return new object[] { new Host.FunctionTimeoutException("Function timed out.") };
81+
yield return new object[] { new Host.FunctionTimeoutException("Function timed out.") };
82+
83+
// FunctionTimeoutAbortException (top-level)
84+
yield return new object[] { new Host.FunctionTimeoutAbortException("Function timed out.") };
5985

6086
// SessionAbortedException as InnerException (e.g. out-of-memory handling)
6187
yield return new object[] { new Exception("Function invocation failed.", new SessionAbortedException("Out of memory")) };
@@ -69,24 +95,50 @@ public static IEnumerable<object[]> PlatformLevelExceptions()
6995

7096
private (OutOfProcMiddleware middleware, DispatchMiddlewareContext context) SetupOrchestratorTest(Exception executorException)
7197
{
72-
var (middleware, dispatchContext) = this.CreateMiddleware(executorException, "TestOrchestrator", FunctionType.Orchestrator);
98+
(OutOfProcMiddleware middleware, DispatchMiddlewareContext dispatchContext)
99+
= this.CreateMiddleware(executorException, "TestOrchestrator", FunctionType.Orchestrator);
73100

74101
var orchestrationState = new OrchestrationRuntimeState(
75-
new List<HistoryEvent>
76-
{
102+
[
77103
new ExecutionStartedEvent(-1, null) { Name = "TestOrchestrator" },
78-
});
104+
]);
79105

80106
dispatchContext.SetProperty(orchestrationState);
81107
dispatchContext.SetProperty(new OrchestrationInstance { InstanceId = "test-instance-id" });
82108

109+
return (middleware, dispatchContext);
110+
}
111+
112+
private (OutOfProcMiddleware middleware, DispatchMiddlewareContext context) SetupEntityTest(Exception executorException)
113+
{
114+
(OutOfProcMiddleware middleware, DispatchMiddlewareContext dispatchContext)
115+
= this.CreateMiddleware(executorException, "TestEntity", FunctionType.Entity);
116+
117+
dispatchContext.SetProperty(new EntityBatchRequest
118+
{
119+
InstanceId = "@TestEntity@test-key",
120+
EntityState = null,
121+
Operations = new List<OperationRequest>(),
122+
});
123+
124+
return (middleware, dispatchContext);
125+
}
126+
127+
private (OutOfProcMiddleware middleware, DispatchMiddlewareContext context) SetupActivityTest(Exception executorException)
128+
{
129+
(OutOfProcMiddleware middleware, DispatchMiddlewareContext dispatchContext)
130+
= this.CreateMiddleware(executorException, "TestActivity", FunctionType.Activity);
131+
132+
dispatchContext.SetProperty(new TaskScheduledEvent(-1) { Name = "TestActivity" });
133+
dispatchContext.SetProperty(new OrchestrationInstance { InstanceId = "test-instance-id" });
134+
83135
return (middleware, dispatchContext);
84136
}
85137

86138
private (OutOfProcMiddleware middleware, DispatchMiddlewareContext context) CreateMiddleware(
87139
Exception executorException, string functionName, FunctionType functionType)
88140
{
89-
var extension = CreateDurableTaskExtension();
141+
DurableTaskExtension extension = CreateDurableTaskExtension();
90142

91143
var mockExecutor = new Mock<ITriggeredFunctionExecutor>();
92144
mockExecutor
@@ -131,15 +183,14 @@ private static DurableTaskExtension CreateDurableTaskExtension()
131183
new OptionsWrapper<DurableTaskOptions>(options),
132184
NullLoggerFactory.Instance,
133185
TestHelpers.GetTestNameResolver(),
134-
new[]
135-
{
186+
[
136187
new AzureStorageDurabilityProviderFactory(
137188
new OptionsWrapper<DurableTaskOptions>(options),
138189
new TestStorageServiceClientProviderFactory(),
139190
TestHelpers.GetTestNameResolver(),
140191
NullLoggerFactory.Instance,
141192
TestHelpers.GetMockPlatformInformationService()),
142-
},
193+
],
143194
new TestHostShutdownNotificationService(),
144195
new DurableHttpMessageHandlerFactory(),
145196
platformInformationService: TestHelpers.GetMockPlatformInformationService());
@@ -148,13 +199,13 @@ private static DurableTaskExtension CreateDurableTaskExtension()
148199
private static WorkItemMetadata CreateWorkItemMetadata(bool isExtendedSession, bool includeState)
149200
{
150201
// WorkItemMetadata has an internal constructor, so we use reflection to create it.
151-
var ctor = typeof(WorkItemMetadata).GetConstructor(
202+
ConstructorInfo ctor = typeof(WorkItemMetadata).GetConstructor(
152203
BindingFlags.Instance | BindingFlags.NonPublic,
153204
binder: null,
154-
new[] { typeof(bool), typeof(bool) },
205+
[typeof(bool), typeof(bool)],
155206
modifiers: null);
156207
Assert.NotNull(ctor);
157-
return (WorkItemMetadata)ctor.Invoke(new object[] { isExtendedSession, includeState });
208+
return (WorkItemMetadata)ctor.Invoke([isExtendedSession, includeState]);
158209
}
159210

160211
/// <summary>
@@ -163,12 +214,9 @@ private static WorkItemMetadata CreateWorkItemMetadata(bool isExtendedSession, b
163214
/// <c>WorkerProcessExitException</c> lives in <c>Microsoft.Azure.WebJobs.Script</c>
164215
/// (the Functions host runtime), which is too heavy to reference as a test dependency.
165216
/// </summary>
166-
private class WorkerProcessExitExceptionStub : Exception
217+
private class WorkerProcessExitExceptionStub(string message)
218+
: Exception(message)
167219
{
168-
public WorkerProcessExitExceptionStub(string message)
169-
: base(message)
170-
{
171-
}
172220
}
173221
}
174222
}

0 commit comments

Comments
 (0)