dotnet / dotnet/aspnetcore

Memory Leak in HostingApplication/WebApplication during integrationtest

Open
#54,342 4 comments 0 reactions 0 assignees View on GitHub
area-networking
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Describe the bug

When running integrationtests memory usage accumulates besides the WebApplication is disposed.

![image](https://github.com/dotnet/aspnetcore/assets/6747376/e9966382-4e60-4e4d-b24e-bd8e6f12aa5d)

there are also a bunch of classes kept alive by service provider internals again my pov is that when the webapplication is disposed everything should be cleaned up. I can see the serviceProvider is disposed but it still contains a buch of stuff that keeps objects alive.

![image](https://github.com/dotnet/aspnetcore/assets/6747376/1adac104-b026-4401-8f5b-109c72ad6cef)

I tried to manually cleanup stuff which improved the situation but not really cleaned it up all the way there are still classes that are kept by for example som DFAMatcher

![image](https://github.com/dotnet/aspnetcore/assets/6747376/035fd16d-8727-45b6-afdd-28ffb6fe67a9)

here is my code I use to improve dispose behavior. Im not sure which are sideffects and should be gone automatically once the webapplication is no longer kept alive but its hard to see if this kind of large objects are kept alive.

```csharp
var server = _host.Services.GetRequiredService();

_host?.StopAsync().GetAwaiter().GetResult();
_host?.Dispose();

var callSiteFactory = serviceProvider.GetType().GetProperty("CallSiteFactory", BindingFlags.Instance | BindingFlags.NonPublic)!;

var callSiteFactoryValue = callSiteFactory.GetValue(serviceProvider)!;
var descriptors = callSiteFactoryValue.GetType().GetField("_descriptors", BindingFlags.Instance | BindingFlags.NonPublic)!;
var descriptorLookup = callSiteFactoryValue.GetType().GetField("_descriptorLookup", BindingFlags.Instance | BindingFlags.NonPublic)!;

var descriptorsValue = (IList)descriptors.GetValue(callSiteFactoryValue)!;
descriptorsValue.Clear();

var dictionary = descriptorLookup.GetValue(callSiteFactoryValue)!;
var clearDictionary = dictionary.GetType().GetMethod("Clear")!;
clearDictionary.Invoke(dictionary, new object[0]);

var callSiteCache = callSiteFactoryValue.GetType().GetField("_callSiteCache", BindingFlags.Instance | BindingFlags.NonPublic)!;
var callSiteCacheDictionary = callSiteCache.GetValue(callSiteFactoryValue)!;
var clearcallSiteCacheDictionary = callSiteCacheDictionary.GetType().GetMethod("Clear")!;
clearcallSiteCacheDictionary.Invoke(callSiteCacheDictionary, new object[0]);

var callSiteLocks = callSiteFactoryValue.GetType().GetField("_callSiteLocks", BindingFlags.Instance | BindingFlags.NonPublic)!;
var callSiteLocksDictionary = callSiteLocks.GetValue(callSiteFactoryValue)!;
var clearcallSiteLocksDictionary = callSiteLocksDictionary.GetType().GetMethod("Clear")!;
clearcallSiteLocksDictionary.Invoke(callSiteLocksDictionary, new object[0]);

var serviceAccessors = serviceProvider.GetType().GetField("_serviceAccessors", BindingFlags.Instance | BindingFlags.NonPublic)!;
var serviceAccessorsDictionary = serviceAccessors.GetValue(serviceProvider)!;
var serviceAccessorsClearDictionary = serviceAccessorsDictionary.GetType().GetMethod("Clear")!;
serviceAccessorsClearDictionary.Invoke(serviceAccessorsDictionary, new object[0]);

var host = _host!.GetType().GetField("_host", BindingFlags.Instance | BindingFlags.NonPublic)!;
var hostValue = host.GetValue(_host)!;

var hostedServices = hostValue!.GetType().GetField("_hostedServices", BindingFlags.Instance | BindingFlags.NonPublic)!;
hostedServices.SetValue(hostValue, null);

var options = server.GetType().GetProperty("Options", BindingFlags.Instance | BindingFlags.Public)!;
var optionsValue = options.GetValue(server)!;
var codeBackedListenOptions = optionsValue.GetType().GetProperty("CodeBackedListenOptions", BindingFlags.Instance | BindingFlags.NonPublic)!;
var codeBackedListenOptionsValue = (IEnumerable)codeBackedListenOptions.GetValue(optionsValue)!;

foreach (var listenOption in codeBackedListenOptionsValue)
{
var middleware = listenOption.GetType().BaseType!.GetField("_middleware", BindingFlags.Instance | BindingFlags.NonPublic)!;
var middlewareValue = (IList)middleware.GetValue(listenOption)!;
middlewareValue.Clear();
}

var addressBindingContext = server.GetType().GetProperty("AddressBindContext", BindingFlags.Instance | BindingFlags.NonPublic)!;
addressBindingContext.SetValue(server, null);
```

### Expected Behavior

Disposing the webapplication should free all related Memory.

### Steps To Reproduce

_No response_

### Exceptions (if any)

_No response_

### .NET Version

8.0.2

### Anything else?

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.