sendgrid / sendgrid-csharp

The Official Twilio SendGrid C#, .NetStandard, .NetCore API Library
https://sendgrid.com
MIT License
1.08k stars 585 forks source link

Excessive "HttpMessageHandler cleanup cycle" Logs in Long-Running Service Injecting ISendGridClient #1213

Open sdepouw opened 2 weeks ago

sdepouw commented 2 weeks ago

I have a Windows Service written in .NET 8 that uses C# classes, some of which use Dependency Injection (the AddSendGrid() extension) to get an instance of ISendGridClient. It seems that when using this in a Windows service, or otherwise a C# class that's loaded once and remains active for the entire application's runtime (basically think of it being used in a Singleton), our application logs are flooded with messages like this (note InjectableSendGridClient:

[07:14:06 DBG] Hosting started
[07:16:06 DBG] HttpMessageHandler expired after 120000ms for client 'InjectableSendGridClient'
[07:16:16 DBG] Starting HttpMessageHandler cleanup cycle with 1 items
[07:16:16 DBG] Ending HttpMessageHandler cleanup cycle after 0.0233ms - processed: 0 items - remaining: 1 items
[07:16:26 DBG] Starting HttpMessageHandler cleanup cycle with 1 items
[07:16:26 DBG] Ending HttpMessageHandler cleanup cycle after 0.0061ms - processed: 0 items - remaining: 1 items
[07:16:36 DBG] Starting HttpMessageHandler cleanup cycle with 1 items
[07:16:36 DBG] Ending HttpMessageHandler cleanup cycle after 0.0032ms - processed: 0 items - remaining: 1 items
[07:16:46 DBG] Starting HttpMessageHandler cleanup cycle with 1 items
[07:16:46 DBG] Ending HttpMessageHandler cleanup cycle after 0.0035ms - processed: 0 items - remaining: 1 items
[07:16:56 DBG] Starting HttpMessageHandler cleanup cycle with 1 items
[07:16:56 DBG] Ending HttpMessageHandler cleanup cycle after 0.0034ms - processed: 0 items - remaining: 1 items
[07:17:06 DBG] Starting HttpMessageHandler cleanup cycle with 1 items
[07:17:06 DBG] Ending HttpMessageHandler cleanup cycle after 0.006ms - processed: 0 items - remaining: 1 items
[07:17:16 DBG] Starting HttpMessageHandler cleanup cycle with 1 items

As you can see, after the given class is alive for 2 minutes, the cleanup cycle runs every 10 seconds after the fact. It appears that ISendGridClient is keeping a single HttpClient instance alive for the entire duration the ISendGridClient remains in scope.

Is there a way to use ISendGridClient in a disposable context? Something like using(ISendGridClient client = someFactory.CreateClient())? Or, would it be possible that, when injecting ISendGridClient via dependency injection (i.e. constructor injection) for it to manage its internal client so it isn't "kept alive" for long-running services/processes? Some way to support using ISendGridClient in singleton/long-running scenarios?

Note that I cannot simply "turn off logging at the Debug level" in this scenario.

Here's how we're setting up dependency injection in Program.cs:

using Microsoft.Extensions.Hosting;
using SendGrid.Extensions.DependencyInjection;

HostApplicationBuilder builder = Host.CreateApplicationBuilder();

builder.Services.AddSendGrid(opts => opts.ApiKey = "my-api-key");

Here's how we're using the class:

using SendGrid;

public class SendGridEmailService(ILogger<SendGridEmailService> logger, ISendGridClient client)
{
  // ISendGridClient used in this class
}

Current Workaround

Presently my only idea is to inject IServiceProvider instead, then get the client at runtime.

public class SendGridEmailSErvice(ILogger<SendGridEmailService> logger, ISerivceProvider provider)
{
  public async Task SendMailAsync(SendGridMessage message, CancellationToken cancellationToken)
  {
    ISendGridClient sendGridClient = provider.GetRequiredService<ISendGridClient>();
    await sendGridClient.SendEmailAsync(message, cancellationToken);
  }
}