I think I'm facing a possible memory leak with the GCP PubSub PublisherClient implementation in .NET Core. I wrote "I think" because to be fair I'm not even sure I'm using it properly (and this is why I'm posting this question here).
The app is a .NET Core Web API. Here's an extract of the implementation I have:
public class GoogleTopicPublisher : IAsyncDisposable
{
private readonly TopicName _topic;
private readonly string _apiToken;
private readonly ILogger<GoogleTopicPublisher> _logger;
private readonly Lazy<Task<PublisherClient>> _lazyPublisher;
public GoogleTopicPublisher(string projectId, string topicId, string apiToken,
ILogger<GoogleTopicPublisher> logger)
{
_apiToken = apiToken;
_topic = new TopicName(projectId, topicId);
_logger = logger;
_lazyPublisher = new Lazy<Task<PublisherClient>>(() => PublisherClient.CreateAsync(_topic));
}
public async Task PublishAsync(PubSubEnvelope envelope)
{
var messageData = Newtonsoft.Json.JsonConvert.SerializeObject(envelope);
var message = new PubsubMessage()
{
Data = ByteString.CopyFromUtf8(messageData),
Attributes =
{
{"token", _apiToken }
}
};
var publisher = await _lazyPublisher.Value;
await publisher.PublishAsync(message);
GC.Collect();
GC.WaitForPendingFinalizers();
}
public async ValueTask DisposeAsync()
{
if (!_lazyPublisher.IsValueCreated)
return;
var publisher = _lazyPublisher.Value.Result;
await publisher.ShutdownAsync(TimeSpan.FromSeconds(15));
}
}
The whole GoogleTopicPublisher
class is registered as Singleton. As you can see, I'm instantiating the Publisher using an async Lazy to avoid creating a new instance every time I have to publish a message.
The pain point is the calls to the GC: without them, I can see the memory usage growing and growing, till the whole application runs out of memory and gets restarted.
Now my questions:
- is it a good practice to instantiate the PublisherClient this way? If not, is there a better way?
- can I avoid in any way the calls to the GC?
thanks!