Azure-Functions icon indicating copy to clipboard operation
Azure-Functions copied to clipboard

Migrating azure functions to .net8 isolated, AspNetCore HttpTriggers Hang

Open wjchristenson2 opened this issue 6 months ago • 7 comments

Multiple people are reporting an issue after migrating from net6 in-process to net8 isolated. A previous issue was opened but closed, so I am creating a new issue here.

I believe it is related to AspNetCore. If you refactor the httpTrigger to use the built-in http model (HttpResponseData, HttpRequestData) and ConfigureFunctionsWorkerDefaults() the function works. If you enable AspNetCore ConfigureFunctionsWebApplication() and kill the built-in http model... the AspNetCore http trigger hangs/breakpoint never hit. Weird thing is, if you create a brand new isolated .NET 8 function app and enable AspNetCore + build out the same httpTrigger function, it works. I have mirrored all config, references, project settings between the new function project vs the migrated. No go. I've deleted all /obj /bin directories + clean, rebuild, restart and it does not help. At this point, I think its an environment issue - maybe something is cached w/ net6 on the machine. I'm not going to migrate to net8 if it means all devs have to reinstall VS or the like if that is the issue.

wjchristenson2 avatar Dec 29 '23 22:12 wjchristenson2

Found the issue. You cannot have comments in the host.json file. AspNetCore Kestrel server will throw JSON deserialization exceptions. I've attached the debug output for AI from Visual Studio which shows the exception. Took a bit to figure out what was causing it. Seems it crashed the Kestrel AspNetCore server/service which would explain why calls to the Azure Function would hang. Prior in-process AspNetCore function httpTriggers using the same host.json file did not have this issue.

error-log-2024-01-22.txt

wjchristenson2 avatar Jan 22 '24 21:01 wjchristenson2

Facing this issue right now after moving my Azure Function app project from .NET6 in-process to .NET 8 isolated model but I don't have any comment in my host.json file. The only weird thing I see is this in the output about a null value for paraemter 'httpMethods' (even though I see that the attribute has the get value there):

Application Insights Telemetry (unconfigured): {"name":"AppExceptions","time":"2024-02-26T13:57:32.1614774Z","tags":{"ai.application.ver":"1.0.0.0","ai.cloud.roleInstance":"MNBXXX","ai.operation.id":"446775881d196e61ada8eddc83bf467b","ai.operation.parentId":"a3248540beeb3381","ai.internal.sdkVersion":"azurefunctions-netiso: 1.2.0+8a4944ddc2956da7182833ce4917d8fb1d7","ai.internal.nodeName":"localhost:7088"},"data":{"baseType":"ExceptionData","baseData":{"ver":2,"exceptions":[{"id":10366524,"outerId":0,"typeName":"System.ArgumentNullException","message":"Value cannot be null. (Parameter 'httpMethods')","hasFullStack":true,"parsedStack":[{"level":0,"method":"System.ArgumentNullException.Throw","assembly":"System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e","line":0},{"level":1,"method":"System.ArgumentNullException.ThrowIfNull","assembly":"System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e","line":0},{"level":2,"method":"Microsoft.AspNetCore.Routing.HttpMethodMetadata..ctor","assembly":"Microsoft.AspNetCore.Routing, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60","line":0},{"level":3,"method":"Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.AspNetMiddleware.FunctionsEndpointDataSource.MapHttpFunction","assembly":"Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore, Version=1.2.1.0, Culture=neutral, PublicKeyToken=551316b6919f366c","fileName":"D:\\a\\_work\\1\\s\\extensions\\Worker.Extensions.Http.AspNetCore\\src\\AspNetMiddleware\\FunctionsEndpointDataSource.cs","line":108},{"level":4,"method":"Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.AspNetMiddleware.FunctionsEndpointDataSource.BuildEndpoints","assembly":"Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore, Version=1.2.1.0, Culture=neutral, PublicKeyToken=551316b6919f366c","fileName":"D:\\a\\_work\\1\\s\\extensions\\Worker.Extensions.Http.AspNetCore\\src\\AspNetMiddleware\\FunctionsEndpointDataSource.cs","line":63},{"level":5,"method":"Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.AspNetMiddleware.FunctionsEndpointDataSource.get_Endpoints","assembly":"Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore, Version=1.2.1.0, Culture=neutral, PublicKeyToken=551316b6919f366c","fileName":"D:\\a\\_work\\1\\s\\extensions\\Worker.Extensions.Http.AspNetCore\\src\\AspNetMiddleware\\FunctionsEndpointDataSource.cs","line":44},{"level":6,"method":"Microsoft.AspNetCore.Routing.CompositeEndpointDataSource.CreateEndpointsUnsynchronized","assembly":"Microsoft.AspNetCore.Routing, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60","line":0},{"level":7,"method":"Microsoft.AspNetCore.Routing.CompositeEndpointDataSource.EnsureEndpointsInitialized","assembly":"Microsoft.AspNetCore.Routing, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60","line":0},{"level":8,"method":"Microsoft.AspNetCore.Routing.CompositeEndpointDataSource.get_Endpoints","assembly":"Microsoft.AspNetCore.Routing, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60","line":0},{"level":9,"method":"Microsoft.AspNetCore.Routing.DataSourceDependentCache`1.Initialize","assembly":"Microsoft.AspNetCore.Routing, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60","line":0},{"level":10,"method":"System.Threading.LazyInitializer.EnsureInitializedCore","assembly":"System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e","line":0},{"level":11,"method":"Microsoft.AspNetCore.Routing.Matching.DataSourceDependentMatcher..ctor","assembly":"Microsoft.AspNetCore.Routing, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60","line":0},{"level":12,"method":"Microsoft.AspNetCore.Routing.Matching.DfaMatcherFactory.CreateMatcher","assembly":"Microsoft.AspNetCore.Routing, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60","line":0},{"level":13,"method":"Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.InitializeCoreAsync","assembly":"Microsoft.AspNetCore.Routing, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60","line":0},{"level":14,"method":"System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw","assembly":"System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e","line":0},{"level":15,"method":"System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification","assembly":"System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e","line":0},{"level":16,"method":"System.Runtime.CompilerServices.TaskAwaiter`1.GetResult","assembly":"System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e","line":0},{"level":17,"method":"Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware+<<Invoke>g__AwaitMatcher|10_0>d.MoveNext","assembly":"Microsoft.AspNetCore.Routing, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60","line":0},{"level":18,"method":"System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw","assembly":"System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e","line":0},{"level":19,"method":"System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification","assembly":"System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e","line":0},{"level":20,"method":"Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol+<ProcessRequests>d__238`1.MoveNext","assembly":"Microsoft.AspNetCore.Server.Kestrel.Core, Version=8.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60","line":0}]}],"severityLevel":"Error","properties":{"ConnectionId":"0HN1MQBGBAGV8","RequestId":"0HN1MQBGBAGV8:00000001","_MS.ProcessedByMetricExtractors":"(Name:'Exceptions', Ver:'1.1')","FormattedMessage":"Connection id \"0HN1MQBGBAGV8\", Request id \"0HN1MQBGBAGV8:00000001\": An unhandled exception was thrown by the application.","DeveloperMode":"true","EventId":"13","RequestPath":"/api/TestHttp","TraceIdentifier":"0HN1MQBGBAGV8:00000001","OriginalFormat":"Connection id \"{ConnectionId}\", Request id \"{TraceIdentifier}\": An unhandled exception was thrown by the application.","CategoryName":"Microsoft.AspNetCore.Server.Kestrel","EventName":"ApplicationError"}}}}

dggmez avatar Feb 26 '24 14:02 dggmez

but I don't have any comment in my host.json file

I got caught out by this a couple of times and I think it was due to a trailing comma. It looks like the ASP model uses a strict JSON parser, so anything that regular JSON would fail to parse causes this.

JakeStanger avatar Feb 26 '24 14:02 JakeStanger

The two JSON files are fine.

Looks like this guy got the same error ( System.ArgumentNullException: Value cannot be null. (Parameter 'httpMethods') ) I'm getting:

https://www.azurebrasil.cloud/azure-functions-system-argumentnullexception-value-cannot-be-null-parameter-httpmethods/

The article is in Portuguese but basically he's saying he found no way to get ConfigureFunctionsWebApplication working in .NET 8 isolated process. I'm on the same boat. T.T

dggmez avatar Feb 26 '24 15:02 dggmez

So the argument null exception I'm getting is being throw by the HttpMethodMetadata constructor at this line of code:

https://github.com/Azure/azure-functions-dotnet-worker/blob/a948d719fcd1358fe3246f1f01021c2b67030a13/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionsEndpointDataSource.cs#L108

More specifically for some reason the functionBinding.Methods property is null even though I'm setting a value in the HttpTrigger attribute:

public async Task<SignalRMessageAction[]> RunAsync(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "LOL")] HttpRequest req)

I originally had no value for Route but I wanted to see what would happen if I set one and the value was read correctly by this line:

https://github.com/Azure/azure-functions-dotnet-worker/blob/a948d719fcd1358fe3246f1f01021c2b67030a13/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionsEndpointDataSource.cs#L99

I also tried setting multiple HTTP methods ("get", "post", "put") and they were recognized by the function (so basically I sent GET, POST and PUT requests to the function and it executed while the DELETE request was not) BUT I still got the argument null exception from the line of code I linked originally.

So given all the above I think something weird is happening here:

https://github.com/Azure/azure-functions-dotnet-worker/blob/a948d719fcd1358fe3246f1f01021c2b67030a13/extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionsEndpointDataSource.cs#L90

I think for my function the JSON string stored in functionMetadata.RawBindings doesn't have the methods property or at it least it doesn't have the array value with the methods I defined in the HttpTrigger attribute.

BTW, is this bug never going to be at least read by MS? It seems nobody from there has ever checked this.

dggmez avatar Mar 13 '24 04:03 dggmez

FINALLY FIGURED IT OUT

After loading the symbols for Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore and debugging FunctionsEndpointDataSource I found that I had another function with an HttpTrigger that had no HTTP methods. So even though I was sending a GET request for my function the mapping is done for all the HttpTrigger functions during the first request. Now, what was the function called? It was the negotiate SignalR function I created based on the OFFICIAL DOCS:

https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-signalr-service-input?tabs=isolated-process&pivots=programming-language-csharp

As you can see there the negotiate function for the isolated worker model is this one:

[Function("Negotiate")]
public static string Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequestData req,
[SignalRConnectionInfoInput(HubName = "serverless", UserId = "{headers.x-ms-client-principal-id}")] string connectionInfo)
{
    // The serialization of the connection info object is done by the framework. It should be camel case. The SignalR client respects the camel case response only.
    return connectionInfo;
}

It seems the official example doesn't work if you are using ConfigureFunctionsWebApplication() and HttpRequest (ASP.NET Core integration). This brings out the question: is this a bug in FunctionsEndpointDataSource? It seems that defining the HTTP methods in a HttpTrigger is optional but if you don't do it it breaks silently if you use the ASP.NET Core integration. Meanwhile if you use ConfigureFunctionsWorkerDefaults() it works... why the inconsistency?

BTW I just created a PR to solve this here: https://github.com/Azure/azure-functions-dotnet-worker/pull/2348

dggmez avatar Mar 13 '24 06:03 dggmez

I had the same problem but mine was due to having my HttpTrigger Route set to "/". This worked in the built-in-model but not when using ASP.NET Core Integration. It's basically a hack to force the function to resolve to the root URL.

It's really annoying that we can't use empty routes in Azure Functions. I can do this just fine in the traditional ASP.NET Core Web API.

brennfoster avatar Apr 25 '24 20:04 brennfoster