Apparent Memory Leak when dispatching from HttpClient continuation

Erick Berg 1 Reputation point
2020-12-28T18:59:43.573+00:00

The Issue: After using HttpClient correctly (as a static single instance) over a long time, ConcurrentStack<T>+Node<Object> instances consistently grow in size causing increased memory usage.

The Use-Case: I am polling a device on my local network through HTTP transmission. I use System.Threading.Timer to control the frequency of my requests. The callback for the polling timer is async void so that the await operator can be used for continuation control. The default window dispatcher is used to display text results after the HttpRequest is called.

The Code for Recreation: .NET Framework 4.5 ; WPF ; All standard settings with a single TextBlock created in MainWindow with x:Name="Debug"

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace LeakIsolator 
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        static MainWindow()
        {
            WebClient.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore);
            HttpClient.DefaultRequestHeaders.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue()
            {
                NoStore = true,
                NoCache = true
            };
        }

        public MainWindow()
        {
            InitializeComponent();

            Timer = new Timer(Poll, null, Timeout.Infinite, Timeout.Infinite);
            Loaded += MainWindow_Loaded;
        }

        private static HttpClient HttpClient { get; } = new HttpClient();

        private static WebClient WebClient { get; } = new WebClient();

        private Timer Timer { get; }

        private void SetText(string text)
        {
            Dispatcher.Invoke(() => Debug.Text = text);
        }

        private void AddText(string text)
        {
            Dispatcher.Invoke(() => Debug.Text += text);
        }

        private async void Poll(object a)
        {
            try
            {
                SetText("Getting status...");
                SetText(await GetResponseStringHttpClient("http://10.10.10.87/status"));
            }
            catch (Exception e)
            {
                SetText($"Exception. {e.Message}");
            }
            finally
            {
                Start();
            }
        }

        private async Task<string> GetResponseStringWebClient(string address)
        {
            return await WebClient.DownloadStringTaskAsync(address);
        }

        private async Task<string> GetResponseStringHttpClient(string address)
        {
            using (var response = await HttpClient.GetAsync(address))
            {
                if (response.IsSuccessStatusCode)
                {
                    AddText(" Success...");
                    return await response.Content.ReadAsStringAsync();
                }
                else
                {
                    AddText(" Fail.");
                    return null;
                }
            }
        }

        private void Start()
        {
            Timer.Change(1000, Timeout.Infinite);
        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            Start();
        }
    }
}

Using ANTS memory profiler, and running the application for 37 minutes in the most recent test, 2.618 MB (small for now, but this is a long-running application) inaccessible-to-me bytes are allocated by a PinnableBufferCache, presumably stored in the gen 2 heap.

I am not sure if this is a memory leak or is a part of something I do not understand. Why am I seeing these memory increases and why am I unable to dictate the local caching behavior of HttpClient and WebClient?

Developer technologies Windows Presentation Foundation
{count} votes

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.