I encountered a very serious problem when switching from Windows to Linux (Ubuntu). The problem is very high memory consumption. I first noticed an increase in memory consumption on Windows when moving from .Net Framework to .Net Core 5. But then this was not much of a problem and was acceptable since the increase was not large. The .Net Framework application consumed approximately 150-200 megabytes and the .Net Core 5 application consumed approximately 250-350 megabytes. But when I switched to Linux, the consumption increased many times over. To make the transition, I switched to .Net 6. After running the application on Linux, I noticed that memory grows over time and does not decrease; with long-term operation of the application, memory consumption can reach 6-8 gigabytes. After doing a memory dump, I noticed that almost 90% was taken up by unmanagement memory. There are about 50 instances of this application running on my server, and they quickly consume all the memory, which leads to application crashes. I have no idea why this is happening, it seems that Garbage collection is not freeing the reserved memory. For information, on Windows under IIS the application worked in 32-bit on Linux in 64-bit.
I tried changing Garbage collection settings such as (DOTNET_GCConserveMemory and DOTNET_gcServer), but it didn’t help.
My application is essentially a CMS hosted using nginx. Basically this is a site using liquid (https://mtirion.medium.com/using-liquid-for-text-base-templates-with-net-80ae503fa635) to parse content. It also runs several hosted services that periodically run tasks such as retrieving data from the database to send email if any, checking for changes in data in a file. Even when no requests are sent to my site (for example at night), memory usage does not decrease. I have done a few things like getting memory dump, dotnet-gcdump, dottrace and dotnetdump but I am not very knowledgeable about it and can provide as per need. https://i.stack.imgur.com/edFAS.png
Here is the code from one of my hosted services:
public override Task Execute()
{
if (_isProcessingEmailSender)
return Task.CompletedTask;
_isProcessingEmailSender = true;
return Task.Run(() =>
{
try
{
using (var scope = ServiceActivator.GetScope())
{
var dataContext = scope?.ServiceProvider?.GetRequiredService<IDataContext>();
if (dataContext == null) return;
var readyToSent = GetReadyToSend(dataContext);
if (readyToSent.Any())
{
var emailService = scope?.ServiceProvider?.GetRequiredService<IEmailService>();
if (emailService == null)
return;
var messagesToSend = new Queue<IEmailQueue>(readyToSent);
SendMessages(messagesToSend, dataContext, emailService);
}
}
_isProcessingEmailSender = false;
}
catch (Exception ex)
{
_isProcessingEmailSender = false;
Log.Error(ex, $"Error while sending emails. Error: {ex.Message}");
}
finally
{
_isProcessingEmailSender = false;
}
});
}
private void SendMessages(Queue<IEmailQueue> messages, IDataContext dataContext, IEmailService emailService)
{
while (messages.Any())
{
var email = messages.Dequeue();
Send(email, dataContext, emailService);
}
}
private IList<IEmailQueue> GetReadyToSend(IDataContext dataContext)
{
var maxAttempts = _treeplSettings.EmailSender.MaxAttemptsCount;
var updatedDateTime = DateTime.UtcNow.AddSeconds(-15);
var countLimit = 15;
var virtualPoints = dataContext.DatabaseProvider.GetPreparedEmail(maxAttempts, updatedDateTime, countLimit);
if (!virtualPoints.Any())
return new List<IEmailQueue>();
return dataContext.Query<IEmailQueue>()
.Where(e => virtualPoints.Any(vp => e.VirtualPointer.InstanceId == vp)).ToList();
}
private void Send(IEmailQueue message, IDataContext dataContext, IEmailService emailService)
{
if (message == null)
{
return;
}
message.SendAttempts++;
try
{
Log.Information($"Sending email. Email Id {message?.VirtualPointer.Pointer.ToString()}, message to: {message?.To}, message body: {message?.Body}");
if (TrySend(message.ToMailMessage(), emailService, out string errorMessage, out string sentServer))
{
message.State = EmailMessageState.Sent;
message.SentServer = sentServer;
}
else
{
message.State = EmailMessageState.Failed;
message.ErrorMessage = errorMessage;
}
}
catch (Exception ex)
{
message.State = EmailMessageState.Failed;
message.ErrorMessage = ex.Message;
Log.Error($"Error while sending email. Email Id {message?.VirtualPointer.Pointer.ToString()}, message to: {message?.To}, message body: {message?.Body}");
throw;
}
finally
{
dataContext.SerializeAll();
}
}
public bool TrySend(MailMessage mail, IEmailService emailService, out string errorMessage, out string sentServer)
{
SmtpSettings settings;
var isSent = true;
errorMessage = null;
sentServer = null;
if (CheckMailDomainIsVerified(mail.From, emailService))
{
settings = _treeplSettings.EmailSender.AwsSmtpSettings;
sentServer = "AWS";
}
else
{
settings = _treeplSettings.EmailSender.CustomSmtpSettings;
sentServer = "CUSTOM";
}
using (var client = GetClient(settings))
{
try
{
client.Send(mail);
}
catch (Exception ex)
{
errorMessage = ex.Message;
isSent = false;
throw new Exception(
$"Error while sent email. Subject: {mail.Subject}, From: {mail.From}, To: {string.Join(",", mail.To.Select(x => x.Address))}", ex);
}
}
return isSent;
}
private SmtpClient GetClient(SmtpSettings settings)
{
var client = new SmtpClient(settings.Host, settings.Port)
{
Credentials = new NetworkCredential(settings.Username, settings.Password),
EnableSsl = settings.EnableSsl
};
return client;
}
private bool CheckMailDomainIsVerified(MailAddress from, IEmailService emailService)
{
try
{
var domainKey = $"{from.Host}-MailDomain";
if (_emailDomainCache.TryGetValue(domainKey, out IMailDomain val))
{
return val != null && val.TxtStatus.Equals(SUCCESS);
}
else
{
var domain = emailService.GetIdentityVerificationAttribute(from.Host);
_emailDomainCache.AddOrUpdate(domainKey, domain, (k, v) => domain);
return domain != null && domain.TxtStatus.Equals(SUCCESS);
}
}
catch
{
return false;
}
}
We have absolutely no context for what your application is doing, which makes it very hard to give any help. Are you able to reproduce the problem with a minimal app? Please give far, far more detail.
I have added to the description for a better understanding of the application
My guess is that the Liquid part isn’t being collected, but without a minimal way of reproducing it, or indeed seeing any part of the code, it’s hard to say more.
The Liquid part is used to parse the site page’s content. Not used at night, but memory usage increases. I don’t understand why the same code works fine on Windows, but on Linux it consumes so much memory.