locked
Not disposing HttpResponseMessage when throwing an exception after SendAsync of HttpMessageHandler results blocking of outgoing http requests RRS feed

  • Question

  • User-319677495 posted

    We are seeing an issue where not disposing HttpResponseMessage when throwing an exception after SendAsync of HttpMessageHandler results in blocking of subsequent outgoing http requests. Based on this it seems disposing HttpResponseMessage is not required. So, we wrote a very simple console test app (see code below) that reproduces the problem and played around with it. Here’s what we found:

    1. HttpResponseMessage.Dispose needs to be called only when an exception is thrown after awaiting on SendAsync of HttpMessageHandler. In this scenario failing to call HttpResponseMessage.Dispose will reproduce the problem where the http queue time increases and subsequent requests start timing out. 
    2. However, if an exception is thrown after awaiting on SendAsync of HttpClient (i.e. after the outermost SendAsync) or if no exception is thrown at all then HttpResponseMessage.Dispose is not needed. In such cases the queue time doesn’t increase and requests don’t time out. 
    3. In both of the above scenarios none of the network perf counters showed any abnormal activity except for the “HttpWebRequests Average Queue Time” which went up. The TCP connections also showed nothing unusual. There were exactly the same number of TCP connections as ServicePointManager.DefaultConnectionLimit which is expected. Also, these TCP connections stay in a “ESTABLISHED” state which is also expected. This suggests that as far as TCP layer is concerned everything is going on normally.

    We are not sure if this behavior is by design or if this is some bug in the Httpclient/HttpMessageHandler implementation. See console test app code below for details.

    Test app has a couple of optional switches:

    -t – means throw from HttpMessageHandler.SendAsync

    -d – means call HttpResponseMessage.Dispose before throwing from HttpMessageHandler.SendAsync

    Some sample outputs from the test app:

    All requests complete quickly and successfully when exception is thrown after Httpclient.SendAsync has completed

    >HttpClientTest.exe

    Process ID: 27984

    ServicePointManager.DefaultConnectionLimit: 2

     Finished iteration in 100ms. Simulated exception after Httpclient.SendAsync has completed

    Finished iteration in 103ms. Simulated exception after Httpclient.SendAsync has completed

    ...

     

    Requests time out when exception is thrown from HttpMessageHandler.SendAsync

    >HttpClientTest.exe -t

    Process ID: 25824

    ServicePointManager.DefaultConnectionLimit: 2

    Finished iteration in 79ms. Simulated exception from HttpMessageHandler.SendAsync

    Finished iteration in 82ms. Simulated exception from HttpMessageHandler.SendAsync

    Finished iteration in 100004ms. A task was canceled.

    Finished iteration in 100005ms. A task was canceled.

    ...

    >netstat -a -o | findstr /spin 25824

    114:  TCP    [2001:4898:e0:61:fcf8:d4d4:8df:9a5a]:3331  sea30s02-in-x04:http   ESTABLISHED     25824

    115:  TCP    [2001:4898:e0:61:fcf8:d4d4:8df:9a5a]:3332  sea30s02-in-x04:http   ESTABLISHED     25824

     

    All requests complete quickly and successfully when HttpRequestMessage.Dispose is called before throwing exception from HttpMessageHandler.SendAsync

    >HttpClientTest.exe -t -d

    Process ID: 8196

    ServicePointManager.DefaultConnectionLimit: 2

    Finished iteration in 84ms. Simulated exception from HttpMessageHandler.SendAsync

    Finished iteration in 88ms. Simulated exception from HttpMessageHandler.SendAsync

    ...

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Net;
    using System.Net.Http;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace HttpClientTest
    {
        class Program
        {
            public static bool ThrowExceptionAfterSendAsync { get; set; }
            public static bool DisposeHttpResponseMessage { get; set; }
    
            private static HttpClient httpclient = new HttpClient(new ThrottlingAwareHandler(new HttpClientHandler()));
    
            static void Main(string[] args)
            {
                Console.WriteLine($"Process ID: {Process.GetCurrentProcess().Id}");
                Console.WriteLine($"ServicePointManager.DefaultConnectionLimit: {ServicePointManager.DefaultConnectionLimit}");
    
                for (int i = 0; i < args.Length; i++)
                {
                    if (args[i] == "-t")
                    {
                        ThrowExceptionAfterSendAsync = true;
                    }
                    else if (args[i] == "-d")
                    {
                        DisposeHttpResponseMessage = true;
                    }
                }
    
                Console.WriteLine();
                RunLoop().Wait();
            }
    
            static async Task RunLoop()
            {
                List<Task> tasks = new List<Task>();
    
                for (int i = 0; i < 10; i++)
                {
                    tasks.Add(Task.Run(async () =>
                    {
                        Stopwatch sw = new Stopwatch();
                        Exception ex = null;
                        try
                        {
                            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://www.google.com");
                            sw.Start();
                            HttpResponseMessage response = await httpclient.SendAsync(request);
                            sw.Stop();
                            throw new Exception("Simulated exception after Httpclient.SendAsync has completed");
                        }
                        catch (Exception e)
                        {
                            sw.Stop();
                            ex = e;
                        }
    
                        Console.WriteLine($"Finished iteration in {sw.ElapsedMilliseconds}ms. {ex?.Message}");
                    }));
                }
    
                await Task.WhenAll(tasks);
            }
        }
    
        public class ThrottlingAwareHandler : DelegatingHandler
        {
            public ThrottlingAwareHandler(HttpMessageHandler innerHandler) : base(innerHandler)
            {
            }
    
            protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
    
                // simulate call throttled exception
                if (Program.ThrowExceptionAfterSendAsync)
                {
                    if (Program.DisposeHttpResponseMessage)
                    {
                        response.Dispose();
                    }
    
                    throw new Exception("Simulated exception from HttpMessageHandler.SendAsync");
                }
    
                return response;
            }
        }
    }

    Friday, February 22, 2019 1:05 AM

All replies

  • User36583972 posted

    Hi kulray,

    Thank you for you share your testing on here. This will help other members find a quick reference if they have faced the similar issue.


    Best Regards,

    Yong Lu

    Monday, February 25, 2019 8:11 AM