-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Description
Which Umbraco version are you using?
13.10.1
Bug summary
For months we've been observing increased memory usage and crashes on some of our Umbraco websites. Over a period of time (days, sometimes weeks) we see memory usage steadily rising until the websites crash because the server is out of memory.
I analysed multiple memory dumps and noticed that there were millions of instances of Action<Umbraco.Cms.Core.Configuration.Models.ContentSettings>.
Searching our code for the usage of ContentSettings didn't return any results. In the end, I let Copilot analyse a class from Umbraco that uses IOptionsMonitor and it mentioned that there is a leak when you don't dispose the returned object from the OnChange method.
When I search through the code base I see there is only 1 file (2nd result, DatabaseBuilder.cs) that disposes the returned object correctly. All the other 66 files cause a memory leak.
I'm not an expert in Umbraco's codebase but personally I don't think most classes benefit from using an IOptionsMonitor since the lifetime is often very short lived.
Specifics
To summarize, this is the correct usage for IOptionsMonitor:
using Microsoft.Extensions.Options;
using System;
public class SomeGoodService : IDisposable
{
private readonly IDisposable _onChangeToken;
private ContentSettings _contentSettings;
public SomeGoodService(IOptionsMonitor<ContentSettings> monitor)
{
_contentSettings = monitor.CurrentValue;
// Keep token to dispose later
_onChangeToken = monitor.OnChange(x => _contentSettings = x);
}
public void Dispose()
{
_onChangeToken?.Dispose();
}
}
Make sure that Dispose() is actually called from outside otherwise it would still leak.
This is NOT correct:
public class SomeBadService
{
private ContentSettings _contentSettings;
public SomeBadService(IOptionsMonitor<ContentSettings> monitor)
{
_contentSettings = monitor.CurrentValue;
// DO NOT USE: This leaks memory
monitor.OnChange(x => _contentSettings = x);
}
}
Steps to reproduce
Inject a class in your code that uses IOptionsMonitor for each request and notice that memory keeps rising.
Expected result / actual result
No memory leaks when using IOptionsMonitor.