9

I thought it would be a good idea to use CancellationToken in my controller like so:

[HttpGet("things", Name = "GetSomething")]
public async Task<ActionResult> GetSomethingAsync(CancellationToken ct) {
    var result = await someSvc.LoadSomethingAsync(ct);
    return Ok(result);
}

The issue is that now Azure Application Insights shows a bunch of exceptions of type System.Threading.Tasks.TaskCancelledException: A Task was canceled. as well as the occasional Npgsql.PostgresException: 57014: canceling statement due to user request. This is noise that I don't need.

Application Insights is registered as a service using the standard method - services.AddApplicationInsightsTelemetry(Configuration);.

Attempted Solution

I thought I could jack into the application pipeline and catch the above Exceptions, converting them to normal Responses. I put the code below in various places. It catches any exceptions and if IsCancellationRequested, it returns a dummy response. Otherwise it rethrows the caught exception.

app.Use(async (ctx, next) =>
{
    try { await next(); }
    catch (Exception ex)
    {
        if (ctx.RequestAborted.IsCancellationRequested)
        {       
            ctx.Response.StatusCode = StatusCodes.Status418ImATeapot;
        }
        else { throw; }
    }
});

This code works in that it changes the response. However exceptions are still getting sent to Application Insights.

Requirements

  • I would like to have a solution that uses RequestAborted.IsCancellationRequested over trying to catch specific exceptions. The reason being that if I've already discovered one implementation that throws an exception not derived from OperationCanceledException the possibility exists there are others that will do the same.
  • It doesn't matter if dependency failures still get logged, just that the exceptions thrown as a result of the request getting canceled don't.
  • I don't want to have a try/catch in every controller method. It needs to be put in place in one spot.

Conclusion

I wish I understood Application Insights mechanism of reporting exceptions. After experimenting I feel like trying to catch errors in the Application pipeline isn't the correct approach. I just don't know what is.

Community
  • 1
  • 1
Daniel Gimenez
  • 18,530
  • 3
  • 50
  • 70
  • Can you just do `catch (Exception ex) when (!(ex is TaskCanceledException) && !(ex is OperationCanceledException))`? – crgolden Oct 29 '19 at 21:11
  • Do you just want to abandon exception like "TaskCancelledException"? – Ivan Glasenberg Oct 30 '19 at 02:08
  • 5
    Also, as a general note, it is good to `throw` instead of `throw ex` so you don't lose the stack trace (https://stackoverflow.com/a/730255) – crgolden Oct 30 '19 at 09:16
  • @IvanYang I would prefer to to work on this on the Exception level only because I've already encountered a case where a TaskCancelledException isn't what's getting thrown during a cancelled request. – Daniel Gimenez Oct 30 '19 at 12:52
  • @crgolden I've tried doing things that way to, but it doesn't change the result. Also, thanks for the reminder on the proper way to re-throw. – Daniel Gimenez Oct 30 '19 at 12:53
  • Exception handling is a pet peeve of mine, and I got two articles I link often: https://blogs.msdn.microsoft.com/ericlippert/2008/09/10/vexing-exceptions/ | https://www.codeproject.com/Articles/9538/Exception-Handling-Best-Practices-in-NET | You seem to be convinced that the `TaskCancelledException` and `PostgresException` are vexing exceptions for your case. Or at least that they get logged by AzureInsight is Vexing? – Christopher Nov 06 '19 at 21:27
  • @Christopher, by the definition in your article the exceptions are vexing. They are necessary because the prevent further processing, but they by non-exceptional circumstances. What is further problematic is that even if I catch them in my code as they were still logged by ApplicationInsights. In the end my solution below tests to see if the circumstances that result in the vexing exception occurred and then throw the exception out. (I like that term vexing exception, btw) – Daniel Gimenez Nov 19 '19 at 14:37
  • I am pretty sure sure you had originally written a different exception. Something about SQL, I think? | Anyway, it seems to be a very common pattern in multitasking to not accidentally use the result of a opeartion that was canceled or aborted due to user action or exception. Take the RunWorkerCompletedEventArgs used by the BackgroundWorker. It has a property "Cancelled" and one "Error". And accessing "Result" Property will throw an exception. However, it is not a vexing exception as you can easily check for them: `if (!e.Cancelled && e.Error == null)` – Christopher Nov 19 '19 at 16:43

2 Answers2

2

I was able to solve this issue with a TelemetryProcessor.

In order to get access to RequestAborted.IsCancellationRequested in the processor, the HttpContext service needed to be made available by calling services.AddHttpContextAccessor() in Startup.ConfigureServices.

In the processor I prevent any exception telemetry from being processed if IsCancellationRequested is true. Below is a simplified example of my final solution, as eventually I realized I needed to do some more nuanced work with Dependencies and Requests that are out of scope of my original request.

internal class AbortedRequestTelemetryProcessor : ITelemetryProcessor
{
    private readonly IHttpContextAccessor httpContextAccessor;
    private readonly ITelemetryProcessor next;

    public AbortedRequestTelemetryProcessor(
        ITelemetryProcessor next, IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
        this.next = next;
    }

    public void Process(ITelemetry item)
    {
        if (httpContextAccessor.HttpContext?.RequestAborted.IsCancellationRequested == true 
            && item is ExceptionTelemetry)
        {
            // skip exception telemetry if cancellation was requested
            return; 
        }
        // Send everything else:
        next.Process(item);
    }
}
Daniel Gimenez
  • 18,530
  • 3
  • 50
  • 70
  • As all end-points take `CancellationToken ct` as a parameter, then wouldn't a check like `item is ExceptionTelemetry ex && ex.Exception is TaskCanceledException` do the same without injecting `IHttpContextAccessor`? – Alex Klaus Jul 03 '23 at 07:51
0

Have you tried by creating an ExceptionFilter?

public class MyExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        if (context?.Exception.Message == "Did it catch this?")
        {
            context.Result = new BadRequestObjectResult("You have no access.");
        }
    }
}

Inside ConfigureServices method:

services.AddMvc(config =>
        {
            config.Filters.Add(typeof(MyExceptionFilter));
        })

The logic inside the filter is just to give you an example of how you'd want to filter something and propagate a result. In your case, I'm guessing you'll need to somehow swallow the exception tied to the context, so that no exception afterwards is spotted.

Perhaps simply assigning it to null will do the trick?

SpiritBob
  • 2,355
  • 3
  • 24
  • 62