5

I use AddHttpClient() dependency injection to add a named client to a transient service. At times, when I execute netstat -a on the server, I see many connections open with TIME_WAIT or CLOSE_WAIT status. I believe that these connections take up so much resource that, other TCP connections are unable to operate. Is this possible? Is there a way to stop these, and is it safe?

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        ServicePointManager.DefaultConnectionLimit = 200;

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

        services.AddHttpClient(FirebaseService.FirebaseServiceClient, ConfigureFirebaseClient);

        services.AddTransient<FirebaseService>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseMvc();
    }

    void ConfigureFirebaseClient(HttpClient client)
    {
        var scopes = new string[] { "https://www.googleapis.com/auth/firebase.messaging" };

        Stream certificateStream = File.OpenRead("firebase-adminsdk.json");

        var serviceCredentials = GoogleCredential.FromStream(certificateStream);
        certificateStream.Close();

        var scopedCredentials = serviceCredentials.CreateScoped(scopes);
        var token = scopedCredentials.UnderlyingCredential.GetAccessTokenForRequestAsync().GetAwaiter().GetResult();
        client.SetBearerToken(token);
    }
}

public class FirebaseService
{
    public static string FirebaseServiceClient = "FirebaseServiceClient";

    private HttpClient _client;

    private readonly ILogger<FirebaseService> _logger;
    private readonly string _messagingUrl; 

    public FirebaseService(
        ILogger<FirebaseService> logger,
        IHttpClientFactory clientFactory)
    {
        _logger = logger;
        _messagingUrl = "https://fcm.googleapis.com/v1/projects/test2/messages:send";
        _client = clientFactory.CreateClient(FirebaseServiceClient);
    }

    public async Task<string> PostToFirebase(Dictionary<string, string> payload)
    {
        HttpResponseMessage result = null;
        string cont = null;
        try
        {
            var content = JsonConvert.SerializeObject(payload, Formatting.None);
            var stringContent = new StringContent(content, Encoding.UTF8, "application/json");

            result = await _client.PostAsync(_messagingUrl, stringContent);
            cont = await result.Content.ReadAsStringAsync();
            return cont;
        }
        finally
        {
            result?.Dispose();
        }
    }

}

public class ValuesController : ControllerBase
{
    private readonly IServiceProvider _serviceProvider;

    public ValuesController(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    [HttpGet]
    public async Task<IActionResult> Get()
    {
        var payload = new Dictionary<string, string>();
        List<Task> tasks = new List<Task>();
        for (int i = 0; i < 100; i++)
        {
            FirebaseService firebaseService = (FirebaseService)_serviceProvider.GetService(typeof(FirebaseService));
            var task = firebaseService.PostToFirebase(payload);
            tasks.Add(task);
            Console.WriteLine(i);
        }

        await Task.WhenAll(tasks.ToArray());

        //Console.WriteLine(result);

        return Ok();
    }

}
9

3 Answers 3

2

I realized that the connections that I had problems with weren't originating from client.PostAsync(). In fact they were from the firebase token authentication requests in Client Configuration action of IHttpClientFactory. So that's why when I switched to a singleton or a static property, CreateClient(clientName) wasn't called more than once, and the problem was gone. Although this was written in the documentation clearly, I had missed it. Each time CreateClient is called, a new instance of HttpClient is created and the configuration action is called.

Sign up to request clarification or add additional context in comments.

Comments

1

CLOSE_WAIT - the other side closed the connection.

TIME_WAIT - the local end point (your app) closed the connection.

Both connections are kept for a few more minutes just in case there are some delayed packets from the other side.

"I believe that these connections take up so much resource that, other TCP connections are unable to operate. Is this possible?" - I think not. They are just keepind a port opened. It depends how many there are. If you have a few hundreds you will be ok.

"Is there a way to stop these, and is it safe?" - I dont think so. They all have the same PID so if you try killing one your all app will shut down.

Looking forward for better answers.

5 Comments

I do thousands of post requests to the same url over a brief amount of time. So when those connections remain open for a few minutes, it accumulates to over 9k open connections After they pass 9k, I start to get socket exceptions.
Can you please, add some some code and tell us what exceptions do you get?
I have created a test repo for this. github.com/ahmettahasakar/HttpClientTest I made it so that there are 100 concurrent posts to Firebase Cloud Messaging. Even though I post blank payloads, the connections still seem to stay open. In order for project to work, you need a Firebase authentication file. You can generate one easily from firebase.google.com/docs/admin/setup There has been some correspondence from Microsoft as well on github.com/dotnet/corefx/issues/35698#issuecomment-468880589
@Ahmet you should have posted that code in the question. It shows you forgot to dispose the response messages
I followed the documentation. Documentation was missing it. I had even asked the writers of the documentation, and they were confused as well. Also, even when I added the dispose, it didn't fix the problem.
-2
+100

Well it's because you might be using it with the wrong lifetime management methodology. HttpClient has a socket exhaustion problem, thus it should be used as a singleton if possible.

This article will answer your question. Also read this about a workaround for DNS changes.

9 Comments

I have read this article. Because of the socket exhaustion problem IHttpClientFactory is introduced. And I'm using that. It's supposed to reuse the same sources, but it does not.
Then youre better off implementing your own, it's pretty easy to register one as a singleton if you are using an IoC container.
I can always to that, but what's the point of using a named client from IHttpClientFactory then?
@Ahmet this answer is simply wrong. HttpClientFactory does cache and reuse HttpClientHandler instances so there's no need to use a singleton. A singleton won't be able to detect DNS changes. That's why HttpClientFactory recycles those handlers after some time, typically 2 minutes
@KristófTóth yes, which is why HttpClientFactory was created, to cache and recycle instances. The issue you linked to says that a singleton should not be used because it doesn't respect DNS changes
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.