Description
Describe the bug
We're noticing a memory issue running a typical ASP.NET Core 6 website behind IIS on a Windows Server. After a few days the worker process (w3wp.exe) memory consumption grows from 2 gigs up to 25 gigs. Performing an IIS Stop / Start "fixes" the issue in the intermediate. I believe the issue is related to a FileWatcher that is not freeing up memory. We've had the issue since .NET 5 and noticed that someone else had a very similar issue (but not necessarily the same) found here #31125 and here #31219. It was supposedly fixed in .NET 6 which is why we upgraded, but we are still having the issue.
Here are some screenshots of dotMemory on the memory data dump when the production server got to 26gigs.
Top level snapshot.
High level inspection page.
Drill down to the Byte[] array section (Similar Retention Section).
Drill down to the Byte[].
Drill down to the OverlappedData section (Instances).
Drill down to an individual OverlappedData.
To Reproduce
I have yet to been able to reproduce this on any of our team's development boxes. This only occurs on production which is making it hard to pinpoint.
Exceptions (if any)
None
Further technical details
- ASP.NET Core version: 6.0.0
- The IDE (VS / VS Code/ VS4Mac) you're running on, and its version: VS2022
- Windows Server 2019 (latest updates)
- SQL Server 2019 (Web Edition)
- IIS 10.0
- ASP.NET Core is running behind IIS in "InProcess"
- Include the output of
dotnet --info
:
dotnet --info Output
.NET SDK (reflecting any global.json):
Version: 6.0.100
Commit: 9e8b04bbff
Runtime Environment:
OS Name: Windows
OS Version: 10.0.19042
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\6.0.100\
Host (useful for support):
Version: 6.0.0
Commit: 4822e3c3aa
.NET SDKs installed:
3.1.120 [C:\Program Files\dotnet\sdk]
3.1.201 [C:\Program Files\dotnet\sdk]
3.1.300 [C:\Program Files\dotnet\sdk]
3.1.301 [C:\Program Files\dotnet\sdk]
3.1.403 [C:\Program Files\dotnet\sdk]
3.1.415 [C:\Program Files\dotnet\sdk]
5.0.104 [C:\Program Files\dotnet\sdk]
5.0.209 [C:\Program Files\dotnet\sdk]
5.0.303 [C:\Program Files\dotnet\sdk]
5.0.400 [C:\Program Files\dotnet\sdk]
5.0.401 [C:\Program Files\dotnet\sdk]
5.0.403 [C:\Program Files\dotnet\sdk]
6.0.100 [C:\Program Files\dotnet\sdk]
.NET runtimes installed:
Microsoft.AspNetCore.All 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.19 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.21 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.10 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.19 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.21 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.10 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.3 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.19 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.20 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.21 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.8 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.9 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.10 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.12 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Including screenshots of the IIS config so we are on the same page / just in case there is something misconfigured that we are unaware of.
Discussion / Side Notes
- I have yet to been able to reproduce this on any developer machine.
- On the first dotMemory screenshot, notice the discrepancy between ".NET total" vs ".NET used". Looks like .NET is allocating more than it needs and then not releasing? On .NET 5 there was significantly more ".NET used", so at least there seems to be some improvement. Regardless, I don't think the app should be using 5gigs, let alone 25 gigs.
- The FileWatcher consumptoin is what is concerning me. We don't use it anywhere in our code. And looking at our libraries, they don't appear to have any type of FileWatcher stuff either. My only though is that it is related to reloadOnChange during the
CreateDefaultBuilder
(https://github.com/dotnet/aspnetcore/blob/main/src/DefaultBuilder/src/WebHost.cs). Similar to issues Memory leak when restarting host on aspnet core 5 #31125 and IHost Memory leak with CreateDefaultBuilder #31219 where they cleared the sources and everything was fine. - We don't do anything "crazy" in the sense of processing - it's a standard ASP.NET website with a SQL database using EF Core. We avoid most of the memory leak pitfalls (e.g. no event subscribing, etc). About the only non-trivial thing we do is run some processes in the background using a hosted service after a request is made. I have included the basic code below in case something pops out that is a big no-no. We more or less copy and pasted the code snippets from https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services
- Stackoverflow post that had a similar issue and resolved it by clearing his config.Sources in ConfigureAppConfiguration. https://stackoverflow.com/questions/66791228/net-5-ihost-filesystemwatcher-memory-leak
- Tagging @davidfowl as he was the one who looked like he worked on the previous bug fix / similar issue. Was the fix for the previous issue (Memory leak when restarting host on aspnet core 5 #31125) added to .NET6?
Code Snippets
Web.config
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<system.webServer xdt:Transform="Replace">
<modules runAllManagedModulesForAllRequests="false">
<remove name="WebDAVModule" />
</modules>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet" arguments=".\App.dll" stdoutLogEnabled="true" stdoutLogFile="..\logs\aspnetcore\stdout" requestTimeout="00:05:00" hostingModel="InProcess">
<environmentVariables>
<environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Production" />
<environmentVariable name="ASPNETCORE_SUPPRESSSTATUSMESSAGES" value="true" />
</environmentVariables>
</aspNetCore>
</system.webServer>
</configuration>
Program.cs
public class Program
{
/// <summary>
///
/// </summary>
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureLogging((hostingContext, logging) =>
{
logging.ClearProviders();
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddEventLog(new EventLogSettings
{
LogName = "Application",
SourceName = hostingContext.Configuration["Name"]
});
switch (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"))
{
case "Production":
// Do nothing
break;
case "Backup":
logging.AddDebug();
break;
case "Staging":
logging.AddDebug();
break;
case "Development":
logging.AddDebug();
break;
default:
throw new Exception("Unsupported environment.");
}
});
webBuilder.UseStartup<Startup>();
});
}
}
Startup.cs (only including this so you can see how to utilize a background hosted service)
...
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
...
services.AddScoped<IScopedWorker, ScopedWorker>();
services.AddSingleton<MySingletonThatAddsToBackground>();
// Follow the host service guide from microsoft.
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
MySingletonThatAddsToBackground.cs
public class MySingletonThatAddsToBackground
{
private readonly IBackgroundTaskQueue _taskQueue;
private readonly ILogger _logger;
private readonly CancellationToken _cancellationToken;
public IServiceProvider _services { get; }
public MySingletonThatAddsToBackground(IServiceProvider services, IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
{
_services = services;
_taskQueue = taskQueue;
_logger = logger;
_cancellationToken = applicationLifetime.ApplicationStopping;
}
public void DoWorkBackground()
{
// Enqueue a background work item
_taskQueue.QueueBackgroundWorkItem(async token =>
{
try
{
using (var scope = _services.CreateScope())
{
var scopedWorker = scope.ServiceProvider.GetRequiredService<IScopedWorker>();
await scopedWorker.DoWork();
}
}
catch (OperationCanceledException)
{
// Prevent throwing if the Delay is cancelled
}
});
}
}
ScopedWorker.cs
public class ScopedWorker : IScopedWorker
{
private readonly ApplicationDbContext _db;
public ScopedWorker(ApplicationDbContext db)
{
_db = db;
}
public void DoWork()
{
var customers = _db.MyCustomers.ToListAsync();
// Do stuff to customers.
await _db.SaveChangesAsync();
}
}
Questions
- Should we not be running it in InProcess mode to get better dump data?
- Should we utilize Workstation GC vs the default Server GC?
- Should we be setting the private memory section on the IIS app pool? All this really does is recycle the app pool which doesn't really solve the issue. To my knowledge, there is no way to restrict an IIS process memory consumption.
- I'm currently using dotMemory on a memory dump from the production box. Is there something else I should be doing to get better insight?