Azure / azure-functions-dotnet-worker

Azure Functions out-of-process .NET language worker
MIT License
419 stars 182 forks source link

ObjectDisposedException: Request has finished and HttpContext disposed. in Extensions.Http.AspNetCore #2417

Open pregress opened 5 months ago

pregress commented 5 months ago

Description

When using the Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore version 1.2.1 we sometimes receive an ObjectDisposedException resulting in a System.InvalidOperationException: An attempt was made to transition a task to a final state when it had already completed.

Here is the stack trace of the ObjectDisposedException

Result: Failure Exception: System.ObjectDisposedException: Request has finished and HttpContext disposed. Object name: 'HttpContext'. at Microsoft.AspNetCore.Http.DefaultHttpContext.ThrowContextDisposed() at Microsoft.AspNetCore.Routing.RoutingHttpContextExtensions.GetRouteData(HttpContext httpContext) at Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.FunctionsHttpProxyingMiddleware.Invoke(FunctionContext context, FunctionExecutionDelegate next) in D:\a_work\1\s\extensions\Worker.Extensions.Http.AspNetCore\src\FunctionsMiddleware\FunctionsHttpProxyingMiddleware.cs:line 54 at Microsoft.Azure.Functions.Worker.FunctionsApplication.InvokeFunctionAsync(FunctionContext context) in D:\a_work\1\s\src\DotNetWorker.Core\FunctionsApplication.cs:line 77 at Microsoft.Azure.Functions.Worker.Handlers.InvocationHandler.InvokeAsync(InvocationRequest request) in D:\a_work\1\s\src\DotNetWorker.Grpc\Handlers\InvocationHandler.cs:line 88 Stack: at Microsoft.AspNetCore.Http.DefaultHttpContext.ThrowContextDisposed() at Microsoft.AspNetCore.Routing.RoutingHttpContextExtensions.GetRouteData(HttpContext httpContext) at Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.FunctionsHttpProxyingMiddleware.Invoke(FunctionContext context, FunctionExecutionDelegate next) in D:\a_work\1\s\extensions\Worker.Extensions.Http.AspNetCore\src\FunctionsMiddleware\FunctionsHttpProxyingMiddleware.cs:line 54 at Microsoft.Azure.Functions.Worker.FunctionsApplication.InvokeFunctionAsync(FunctionContext context) in D:\a_work\1\s\src\DotNetWorker.Core\FunctionsApplication.cs:line 77 at Microsoft.Azure.Functions.Worker.Handlers.InvocationHandler.InvokeAsync(InvocationRequest request) in D:\a_work\1\s\src\DotNetWorker.Grpc\Handlers\InvocationHandler.cs:line 88

This file hase already been changed, so line numbers are wrong. https://github.com/Azure/azure-functions-dotnet-worker/pull/2322/files/6149e16e951188e97bf7fcff6918b45ae233bde4#diff-dc49550c24c24c88566ae78776b0400d415dfc8d1a5d623571903671a953e715

The GetRouteData call in now on line 72 instead of 54.

We don't have a reproducible sample as this exception did only happens seemingly random.

As a quick fix we removed AspNetCore package from our function.

Steps to reproduce

Only saw it on a deployed linux azure function with a single instance.

liliankasem commented 5 months ago

We don't have a reproducible sample as this exception did only happens seemingly random.

Can you share what you have and what you're currently doing? Even if it's random it would be helpful to have some steps that could help us figure out what is causing this.

pregress commented 5 months ago

csproj file:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <AzureFunctionsVersion>v4</AzureFunctionsVersion>
        <OutputType>Exe</OutputType>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <PublishReadyToRun>true</PublishReadyToRun>
    </PropertyGroup>
  <ItemGroup>
      <FrameworkReference Include="Microsoft.AspNetCore.App" />
      <PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.3.1" />
      <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.21.0" />
      <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.1.0" />
      <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="1.2.1" />
      <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.ServiceBus" Version="5.17.0" />
      <PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" Version="4.3.0" />
      <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.17.2" />
      <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" />
      <PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="1.2.0" />
      <PackageReference Include="AspNetCore.HealthChecks.AzureServiceBus" Version="8.0.0" />
      <PackageReference Include="AspNetCore.HealthChecks.Publisher.ApplicationInsights" Version="8.0.0" />
      <PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.0" />
      <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
      <PackageReference Include="Worker.Extensions.HttpTelemetryProcessor" Version="1.0.2" />
  </ItemGroup>

  <ItemGroup>
    <None Update="appsettings.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
    <None Update="appsettings.Production.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="appsettings.Staging.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="host.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
    <None Update="local.settings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CopyToPublishDirectory>Never</CopyToPublishDirectory>
    </None>
  </ItemGroup>
  <PropertyGroup>
    <_FunctionsSkipCleanOutput>true</_FunctionsSkipCleanOutput>
  </PropertyGroup>
    <ItemGroup>
        <Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
    </ItemGroup>
</Project>

Trimmed down function raising object desposed function:

public class BrokenFunction(IRequestProcessor requestProcessor,
    IRequestReader requestReader, IAuthenticationService authenticationService,
    ILogger<BrokenFunction> logger) : AuthorizedFunction(authenticationService)
{
    [Function("BrokenFunction")]
    public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest httpRequest, ExecutionContext executionContext)
    {
        var result = await AuthorizeAsync(httpRequest);
        if (!result.Succeeded)
        {
            logger.LogWarning(result.Failure, "Unauthenticated request");
            return new UnauthorizedResult();
        }

        try
        {
            var request = await requestReader.ReadRequest(logger, httpRequest);
            using var scope = logger.BeginScope(new Dictionary<string, object>
            {
                ["RequestId"] = request.RequestId,
                ["Email"] = httpRequest.HttpContext.User.FindFirst(ClaimTypes.Email)?.Value,
                ["CustomerId"] = httpRequest.HttpContext.User.FindFirst("customer_id")?.Value,
            });
            var response = await requestProcessor.ProcessRequest(logger, request);
            var serializerSettings = SerializerSettings.GetSettings();
            var jsonResponse = JsonConvert.SerializeObject(response, serializerSettings);
            logger.LogInformation("response: {json}", jsonResponse);
            return new OkObjectResult(response);
        }
        catch (UnauthorizedAccessException)
        {
            return new UnauthorizedResult();
        }
    }
}

public abstract class AuthorizedFunction(IAuthenticationService authenticationService)
{
    protected async Task<AuthenticateResult> AuthorizeAsync(HttpRequest httpRequest)
    {
        var result = await authenticationService.AuthenticateAsync(httpRequest.HttpContext, null);
        if (result.Succeeded)
        {
            httpRequest.HttpContext.User = result.Principal;
        }

        return result;
    }
}

Behind the scenes some other api's are being called. They can be slow, but we could not correlate duration vs ObjectDisposedexception.

Rihab8 commented 3 months ago

Hi , I got the similar exception,

Result: Function 'LaunchSubscriptionInstance', Invocation id 'c822cd42-d803-408c-9556-294a93a21f24': An exception was thrown by the invocation.
Exception: System.ObjectDisposedException: Request has finished and HttpContext disposed.
Object name: 'HttpContext'.
   at Microsoft.AspNetCore.Http.DefaultHttpContext.ThrowContextDisposed()
   at Microsoft.AspNetCore.Http.DefaultHttpContext.get_Features()
   at Microsoft.AspNetCore.Routing.RoutingHttpContextExtensions.GetRouteData(HttpContext httpContext)
   at Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.FunctionsHttpProxyingMiddleware.Invoke(FunctionContext context, FunctionExecutionDelegate next) in D:\a\_work\1\s\extensions\Worker.Extensions.Http.AspNetCore\src\FunctionsMiddleware\FunctionsHttpProxyingMiddleware.cs:line 54
   at Microsoft.Azure.Functions.Worker.FunctionsApplication.InvokeFunctionAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\FunctionsApplication.cs:line 89
Stack:    at Microsoft.AspNetCore.Http.DefaultHttpContext.ThrowContextDisposed()
   at Microsoft.AspNetCore.Http.DefaultHttpContext.get_Features()
   at Microsoft.AspNetCore.Routing.RoutingHttpContextExtensions.GetRouteData(HttpContext httpContext)
   at Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.FunctionsHttpProxyingMiddleware.Invoke(FunctionContext context, FunctionExecutionDelegate next) in D:\a\_work\1\s\extensions\Worker.Extensions.Http.AspNetCore\src\FunctionsMiddleware\FunctionsHttpProxyingMiddleware.cs:line 54
   at Microsoft.Azure.Functions.Worker.FunctionsApplication.InvokeFunctionAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\FunctionsApplication.cs:line 89

when we can expect the issue fix ? and is there any short term solution or workaround for this issue

pregress commented 3 months ago

@Rihab8 We removed the dependencies to Asp.net, depending on your functions this is a viable solution or not. If you don't have the asp.net pipeline everything works fine.

javier-acrich commented 1 month ago

Same issue exists in Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore version 1.3.2