From 4b29c59a9fa2952ca953198af974d60cd55477ec Mon Sep 17 00:00:00 2001 From: Fabio Cavalcante Date: Mon, 21 Oct 2024 17:15:19 -0700 Subject: [PATCH] Ignoring fatal exceptions in InvocationHandler (#2789) --- sdk/release_notes.md | 10 ++---- src/DotNetWorker.Core/ExceptionExtensions.cs | 31 ++++++++++++++++ .../Handlers/InvocationHandler.cs | 3 +- .../ExceptionExtensionTests.cs | 36 +++++++++++++++++++ 4 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 src/DotNetWorker.Core/ExceptionExtensions.cs create mode 100644 test/DotNetWorkerTests/ExceptionExtensionTests.cs diff --git a/sdk/release_notes.md b/sdk/release_notes.md index 4988d71c7..67afe6f17 100644 --- a/sdk/release_notes.md +++ b/sdk/release_notes.md @@ -4,14 +4,10 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Sdk 1.18.1 +### Microsoft.Azure.Functions.Worker.Sdk -- Updated `Microsoft.Azure.Functions.Worker.Sdk.Generators` reference to 1.3.4. +- Changed exception handling in function invocation path to ensure fatal exceptions bubble up. -### Microsoft.Azure.Functions.Worker.Sdk.Generators 1.3.4 - -- Changed `FunctionExecutorGenerator` to avoid generation of long `if`/`else` chains for apps with a large number of functions. - -- Use full namespace for `Task.FromResult` in function metadata provider generator to avoid namespace conflict (#2681) +### Microsoft.Azure.Functions.Worker.Sdk.Generators - diff --git a/src/DotNetWorker.Core/ExceptionExtensions.cs b/src/DotNetWorker.Core/ExceptionExtensions.cs new file mode 100644 index 000000000..5080d4f7a --- /dev/null +++ b/src/DotNetWorker.Core/ExceptionExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Azure.Functions.Worker.Core +{ + internal static class ExceptionExtensions + { + public static bool IsFatal(this Exception? exception) + { + while (exception is not null) + { + if (exception + is (OutOfMemoryException and not InsufficientMemoryException) + or AppDomainUnloadedException + or BadImageFormatException + or CannotUnloadAppDomainException + or InvalidProgramException + or AccessViolationException) + { + return true; + } + + exception = exception.InnerException; + } + + return false; + } + } +} diff --git a/src/DotNetWorker.Grpc/Handlers/InvocationHandler.cs b/src/DotNetWorker.Grpc/Handlers/InvocationHandler.cs index 52688ea7a..8ddfaea64 100644 --- a/src/DotNetWorker.Grpc/Handlers/InvocationHandler.cs +++ b/src/DotNetWorker.Grpc/Handlers/InvocationHandler.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Functions.Worker.Context.Features; +using Microsoft.Azure.Functions.Worker.Core; using Microsoft.Azure.Functions.Worker.Grpc; using Microsoft.Azure.Functions.Worker.Grpc.Features; using Microsoft.Azure.Functions.Worker.Grpc.Messages; @@ -113,7 +114,7 @@ public async Task InvokeAsync(InvocationRequest request) response.Result.Status = StatusResult.Types.Status.Success; } - catch (Exception ex) + catch (Exception ex) when (!ex.IsFatal()) { response.Result.Exception = _workerOptions.EnableUserCodeException ? ex.ToUserRpcException() : ex.ToRpcException(); response.Result.Status = StatusResult.Types.Status.Failure; diff --git a/test/DotNetWorkerTests/ExceptionExtensionTests.cs b/test/DotNetWorkerTests/ExceptionExtensionTests.cs new file mode 100644 index 000000000..edf020c44 --- /dev/null +++ b/test/DotNetWorkerTests/ExceptionExtensionTests.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using Microsoft.Azure.Functions.Worker.Core; +using Xunit; + +namespace Microsoft.Azure.Functions.Worker.Tests +{ + public class ExceptionExtensionTests + { + [Theory] + [ClassData(typeof(ExceptionTestData))] + public void IsFatal_ReturnsTrueForFatalExceptions(Exception exception, bool isFatal) + { + var result = exception.IsFatal(); + + Assert.Equal(result, isFatal); + } + + public class ExceptionTestData : TheoryData + { + public ExceptionTestData() + { + Add(new OutOfMemoryException(), true); + Add(new AppDomainUnloadedException(), true); + Add(new BadImageFormatException(), true); + Add(new CannotUnloadAppDomainException(), true); + Add(new InvalidProgramException(), true); + Add(new AccessViolationException(), true); + Add(new InsufficientMemoryException(), false); + Add(new Exception("test", new OutOfMemoryException()), true); + } + } + } +}