Skip to content

Thread safety when using IExceptionHandlerFeature? #3177

Closed
@aymericb

Description

@aymericb

I have code that use ASP.NET Core to implement a JSON API. In order to deal with errors it does something similar to this:

app.UseExceptionHandler(action =>
{
    action.Run(async context =>
    {
        // Apply CORS to error response - workaround for https://github.com/aspnet/CORS/issues/90
        var policy = await app.ApplicationServices.GetService<ICorsPolicyProvider>().GetPolicyAsync(context, ALLOW_ALL);
        var result = cors.EvaluatePolicy(context, policy);
        cors.ApplyResult(result, context.Response);

        // Get exception and log it
        var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;
        await LogException(exception, context.Request);

        // Return API JSON error to the client
        var error = (exception as ApiException)?.Error ?? Models.Error.Unknown;
        await WriteErrorResponse(context, error);
    });
});

I noticed recently that under some circumstances the exception objects that were logged were identical. This occurred when the server was under load and similar exceptions were generated a few ms apart, so it made me think it was a concurrency issue.

After some digging, I believe this problem occurs because access to the IFeatureCollection is not thread safe (see ApplicationInsights-aspnetcore #373), and because there is an await before retrieving the exception from IFeatureCollection.

My understanding of concurrency bug is as follow:

  • The API throw an exception in on of the controllers
  • The ExceptionHandlerMiddleware used by UseExceptionHandler() catches the exception and sets it on the context (context.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature))
  • Because of that CORS workaround, there is an await executed next. I believe this means the code that follows could potentially be executed in a different thread.
  • In the next line I access the FeatureCollection via context.Features.Get<IExceptionHandlerFeature>()?.Error
  • Because the IFeatureCollection is not thread safe, and it reads the IExceptionHandlerFeature in a different thread from where it was written, I end up with corrupted data (i.e. the exception object from another HttpContext)

Does that make sense? Or am I just missing something obvious here? Unfortunately, I don't have definite proof. I was not able to reproduce the problem on a test server.

But if I am right, the whole IExceptionHandlerFeature being set inside the ExceptionHandlerMiddleware is kind of misleading. It would only safe to set features early on when the HttpContext is being initialized, and not in the way it is done for IExceptionHandlerFeature. It looks like the ExceptionHandlerMiddleware should be modified. Maybe it would be better if the Exception object was given as a parameter of the ExceptionHandler callback?

I decided to write my own ErrorMiddleware which is similar to ExceptionHandlerMiddleware but avoids using a Dictionary to capture the exception. I suppose I could have simply reordered the code and ensured the context.Features.Get<IExceptionHandlerFeature>() was executed immediately, and not after the CORS workaround.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions