Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GetRequiredService and AddHttpClient freeze .NET console application (stack overflow)

In my .NET Core console application, this line freezes:

services.GetRequiredService<ISomething>();

in the following code:

interface ISomething { }
class Cool : ISomething
{
    public Cool(IHttpClientFactory factory) { }
}

class Program
{
    static async Task Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();
        await host.StartAsync();

        // Freezes here and maxes out memory
        ISomething internalApiConnector = host.Services.GetRequiredService<ISomething>();

        await host.RunAsync();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureServices((_, services) =>
            {
                services.AddHttpClient<IHttpClientFactory>();
                services.AddSingleton<ISomething, Cool>();
            });
}

and ends up maxing out the memory with eventually crashes the app. What am I missing?

Note:

  • Cool constructor does not get run, so doesn't appear to be something halting in there.

Solution (Thanks all):

serviceCollection.AddHttpClient<IHttpClientFactory>(); needed to be changed to serviceCollection.AddHttpClient();

and

.BuildServiceProvider() was superflous.

like image 520
Jimmyt1988 Avatar asked Jun 21 '26 16:06

Jimmyt1988


1 Answers

What you're experiencing is a stack overflow exception, caused by the AddHttpClient<IHttpClientFactory> registration. This causes a cyclic dependency which is not detected by MS.DI, causing the unfortunate stack overflow.

To understand why this happens, you need to look at the code for HttpClientBuilderExtensions:

builder.Services.AddTransient<TClient>(s =>
{
    var httpClientFactory = s.GetRequiredService<IHttpClientFactory>();
    var httpClient = httpClientFactory.CreateClient(builder.Name);

    var typedClientFactory = s.GetRequiredService<ITypedHttpClientFactory<TClient>>();
    return typedClientFactory.CreateClient(httpClient);
});

As the code shows, a call to AddHttpClient<TClient> results in the registration of a delegate. When that delegate is invoked, an IHttpClientFactory is resolved. From that IHttpClientFactory an HttpClient is created. In your case, however, you specified IHttpClientFactory for your TClient, effectively replacing the original IHttpClientFactory registration. This caused the call to s.GetRequiredService<IHttpClientFactory>() to callback into itself, hence the cyclic dependency and the stack overflow.

To make this more concrete, if we replace TClient with IHttpClientFactory, which is basically what is happening in your code, you can see it more clearly:

// A registration for IHttpClientFactory is made
builder.Services.AddTransient<IHttpClientFactory>(s =>
{
    // When the delegate is called, IHttpClientFactory is resolved.
    var httpClientFactory = s.GetRequiredService<IHttpClientFactory>();
    ...
});

What you did wrong is supplying IHttpClientFactory as the TClient of AddHttpClient<TClient>. AddHttpClient is meant to register a 'client' class that takes a HttpClient as direct dependency. For instance:

services.AddHttpClient<GitHubApiClient>() // GitHubApiClient depends on HttpClient

You, however, aren't the only party at fault here. Microsoft should have been a better job, because:

  • MS.DI's cyclic-dependency detection is lacking and too simplistic causing it to miss this type of cyclic dependency.
  • The AddHttpClient<TClient> method doesn't validate it's preconditions allowing this to happen. As it makes no sense to supply AddHttpClient with an abstract TClient, at the very least it should have prevented this and throw an exception.
  • Further more, it makes no sense to register a TClient that contains no HttpClient as constructor dependency, so this should have been detected by AddHttpClient as well.

This means that there is room for improvement in the Microsoft stack ;-)

like image 175
Steven Avatar answered Jun 23 '26 07:06

Steven



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!