From efd0aefe0a6564759f4aac57be8d6a7b8293ca86 Mon Sep 17 00:00:00 2001 From: sarah <35204912+satvu@users.noreply.github.com> Date: Wed, 12 Jun 2024 16:25:27 -0700 Subject: [PATCH] [Bug Fix] ASP.NET pipeline hangs when function execution throws an unhandled exception (#2527) --- .../release_notes.md | 4 +-- .../FunctionsHttpProxyingMiddleware.cs | 25 +++++++++++-------- .../Worker.Extensions.Http.AspNetCore.csproj | 2 +- .../FunctionsHttpProxyingMiddlewareTests.cs | 17 +++++++++++++ 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md b/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md index 7b0801352..a97b07c00 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md +++ b/extensions/Worker.Extensions.Http.AspNetCore/release_notes.md @@ -4,6 +4,6 @@ - My change description (#PR/#issue) --> -### Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore +### Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore 1.3.2 -- +- Fixes a bug where the invocation hangs when the function has an unhandled exception (#2527). diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs b/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs index 2f939f56b..83dbaeac0 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs @@ -49,19 +49,24 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next // Register additional context features context.Features.Set(FromBodyConversionFeature.Instance); - await next(context); + try + { + await next(context); + + var responseHandled = await TryHandleHttpResult(context.GetInvocationResult().Value, context, httpContext, true) + || await TryHandleOutputBindingsHttpResult(context, httpContext); - var responseHandled = await TryHandleHttpResult(context.GetInvocationResult().Value, context, httpContext, true) - || await TryHandleOutputBindingsHttpResult(context, httpContext); - - if (!responseHandled) + if (!responseHandled) + { + var logger = context.InstanceServices.GetRequiredService(); + logger.NoHttpResponseReturned(context.FunctionDefinition.Name, context.InvocationId); + } + } + finally { - var logger = context.InstanceServices.GetRequiredService(); - logger.NoHttpResponseReturned(context.FunctionDefinition.Name, context.InvocationId); + // Allow ASP.NET Core middleware to continue + _coordinator.CompleteFunctionInvocation(invocationId); } - - // Allow ASP.NET Core middleware to continue - _coordinator.CompleteFunctionInvocation(invocationId); } private static async Task TryHandleHttpResult(object? result, FunctionContext context, HttpContext httpContext, bool isInvocationResult = false) diff --git a/extensions/Worker.Extensions.Http.AspNetCore/src/Worker.Extensions.Http.AspNetCore.csproj b/extensions/Worker.Extensions.Http.AspNetCore/src/Worker.Extensions.Http.AspNetCore.csproj index 8be7ae3a7..f40062bc1 100644 --- a/extensions/Worker.Extensions.Http.AspNetCore/src/Worker.Extensions.Http.AspNetCore.csproj +++ b/extensions/Worker.Extensions.Http.AspNetCore/src/Worker.Extensions.Http.AspNetCore.csproj @@ -6,7 +6,7 @@ ASP.NET Core extensions for .NET isolated functions - 1.3.1 + 1.3.2 net6.0 diff --git a/test/DotNetWorkerTests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs b/test/DotNetWorkerTests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs index 001f5da80..634cdde6d 100644 --- a/test/DotNetWorkerTests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs +++ b/test/DotNetWorkerTests/AspNetCore/FunctionsHttpProxyingMiddlewareTests.cs @@ -1,6 +1,7 @@ // 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 System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -259,5 +260,21 @@ private Mock GetMockIResult() return mockResult; } + + [Fact] + public async Task CompleteFunctionInvocation_RunsWhen_FunctionThrowsException() + { + var test = SetupTest("httpTrigger"); + var mockDelegate = new Mock(); + mockDelegate.Setup(d => d.Invoke(It.IsAny())) + .Throws(new Exception("Custom exception message")); + + var funcMiddleware = new FunctionsHttpProxyingMiddleware(test.MockCoordinator.Object); + + await Assert.ThrowsAsync(async () => await funcMiddleware.Invoke(test.FunctionContext, mockDelegate.Object)); + + mockDelegate.Verify(p => p.Invoke(test.FunctionContext), Times.Once()); + test.MockCoordinator.Verify(p => p.CompleteFunctionInvocation(It.IsAny()), Times.Once()); + } } }