Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In .NET 7 large number of concurrent requests will memory leak #81056

Closed
1 task done
Cricle opened this issue Jan 12, 2023 · 36 comments
Closed
1 task done

In .NET 7 large number of concurrent requests will memory leak #81056

Cricle opened this issue Jan 12, 2023 · 36 comments
Assignees
Milestone

Comments

@Cricle
Copy link

Cricle commented Jan 12, 2023

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

When TFM=.NET7
wrk -t8 -c1000 -d30s --latency http://192.168.2.86:8889 many times, memory will grow to 1G.

When TFM=.NET6
wrk -t8 -c1000 -d30s --latency http://192.168.2.86:8889 many times, memory only 145M.

The Code in program

            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            var builder = WebApplication.CreateBuilder(args);
            
            builder.Services.AddSingleton(new HitokotoManager(Init()));
            builder.Services.AddResponseCompression();

            var app = builder.Build();

            app.UseResponseCompression();
            app.Use(async (HttpContext builder, RequestDelegate next) =>
            {
                    //Write 1kb data in response
            });

Expected Behavior

Memory will not be much larger due to long concurrent requests

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version

7.0.100

Anything else?

.NET SDK:
Version: 7.0.100
Commit: e12b7af219

运行时环境:
OS Name: Windows
OS Version: 10.0.19045
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\7.0.100\

Host:
Version: 7.0.0
Architecture: x64
Commit: d099f07

.NET SDKs installed:
3.1.426 [C:\Program Files\dotnet\sdk]
5.0.303 [C:\Program Files\dotnet\sdk]
5.0.403 [C:\Program Files\dotnet\sdk]
5.0.408 [C:\Program Files\dotnet\sdk]
6.0.112 [C:\Program Files\dotnet\sdk]
6.0.307 [C:\Program Files\dotnet\sdk]
7.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.19 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.31 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.32 [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.11 [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 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.0-preview.4.22251.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.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.19 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.31 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.32 [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.11 [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 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.0-preview.4.22229.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.19 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.30 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.31 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.32 [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.11 [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 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.0-preview.4.22229.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
arm64 [C:\Program Files\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\arm64\InstallLocation]
x86 [C:\Program Files (x86)\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
Not set

global.json file:
Not found

Learn more:
https://aka.ms/dotnet/info

Download .NET:
https://aka.ms/dotnet/download

@adityamandaleeka
Copy link
Member

@Cricle Are you seeing the memory usage go down eventually, or does it stay at 1G on .NET 7?

@Cricle
Copy link
Author

Cricle commented Jan 14, 2023

@Cricle Are you seeing the memory usage go down eventually, or does it stay at 1G on .NET 7?

It stay above 1G

@davidfowl
Copy link
Member

Are you running the application from Visual Studio?

@Cricle
Copy link
Author

Cricle commented Jan 14, 2023

Are you running the application from Visual Studio?

No, I compiled with AOT or docker image run, both like that.

@davidfowl
Copy link
Member

Can you reproduce this running outside of a container? The next step here would be to get a memory profile that compares the 2 applications.

cc @cshung @Maoni0 @mangod9

@Cricle
Copy link
Author

Cricle commented Jan 16, 2023

I tried to reproduce that situation again today.
Compile with AOT, and wrk wrk -t8 -c1000 -d30s --latency http://192.168.2.86:8889, RPS halved, the memory will reach 275MB in a few seconds.
In docker run, after wrk, the static memory will be reduced to 188MB.

The computer has been restarted since the last test.

@Cricle
Copy link
Author

Cricle commented Jan 16, 2023

Brief.zip
Here is the solution

https://hub.docker.com/r/hcricle/brief
Here is docker image

Here is compiled with AOT result
publish.zip

@mangod9
Copy link
Member

mangod9 commented Jan 17, 2023

Hey @Cricle, could you please clarify whether you notice the leak only when compiled as NativeAOT, or without it as well?

@Cricle
Copy link
Author

Cricle commented Jan 18, 2023

Hey @Cricle, could you please clarify whether you notice the leak only when compiled as NativeAOT, or without it as well?

Only AOT

@mangod9
Copy link
Member

mangod9 commented Jan 18, 2023

Ok thanks for clarifying. Adding @MichalStrehovsky @VSadov in case they are aware of anything related to this.

@VSadov
Copy link
Member

VSadov commented Jan 18, 2023

I am not aware of any known leaks specific to NativeAOT. From the description it looks like this is on Windows10.
I wonder if it is possible to create a memory dump of the app when it gets to 400-500 mb.

@davidfowl
Copy link
Member

If the managed memory analysis tools work, we could take a look at what it might be.

@MichalStrehovsky
Copy link
Member

@Cricle do you specify PublishAot anywhere? Looking at the dockerfile this is a r2r+trimmed+singlefile combo and not PublishAot like @mangod9 is suggesting.

Does the problem go away or persist if you remove any of publishsinglefile/publishtrimmed/publishreadytorun?

@Cricle
Copy link
Author

Cricle commented Jan 18, 2023

@Cricle do you specify PublishAot anywhere? Looking at the dockerfile this is a r2r+trimmed+singlefile combo and not PublishAot like @mangod9 is suggesting.

Does the problem go away or persist if you remove any of publishsinglefile/publishtrimmed/publishreadytorun?

publish

Only PublishAOT but it will also.

csproj only

<PublishRelease>true</PublishRelease>

Command

dotnet publish -r win-x64 /p:PublishAot=true

Run debug in vs2022, no problem, stay 220mb

@adityamandaleeka
Copy link
Member

@mangod9 Should this be transferred to the runtime repo?

@mangod9
Copy link
Member

mangod9 commented Jan 23, 2023

yeah its something in the runtime/nativeaot side I am guessing.

@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jan 23, 2023
@adityamandaleeka adityamandaleeka transferred this issue from dotnet/aspnetcore Jan 23, 2023
@ghost
Copy link

ghost commented Jan 23, 2023

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

Issue Details

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

When TFM=.NET7
wrk -t8 -c1000 -d30s --latency http://192.168.2.86:8889 many times, memory will grow to 1G.

When TFM=.NET6
wrk -t8 -c1000 -d30s --latency http://192.168.2.86:8889 many times, memory only 145M.

The Code in program

            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            var builder = WebApplication.CreateBuilder(args);
            
            builder.Services.AddSingleton(new HitokotoManager(Init()));
            builder.Services.AddResponseCompression();

            var app = builder.Build();

            app.UseResponseCompression();
            app.Use(async (HttpContext builder, RequestDelegate next) =>
            {
                    //Write 1kb data in response
            });

Expected Behavior

Memory will not be much larger due to long concurrent requests

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version

7.0.100

Anything else?

.NET SDK:
Version: 7.0.100
Commit: e12b7af219

运行时环境:
OS Name: Windows
OS Version: 10.0.19045
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\7.0.100\

Host:
Version: 7.0.0
Architecture: x64
Commit: d099f07

.NET SDKs installed:
3.1.426 [C:\Program Files\dotnet\sdk]
5.0.303 [C:\Program Files\dotnet\sdk]
5.0.403 [C:\Program Files\dotnet\sdk]
5.0.408 [C:\Program Files\dotnet\sdk]
6.0.112 [C:\Program Files\dotnet\sdk]
6.0.307 [C:\Program Files\dotnet\sdk]
7.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.19 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.31 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.32 [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.11 [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 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.0-preview.4.22251.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.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.19 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.31 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.32 [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.11 [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 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.0-preview.4.22229.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.19 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.30 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.31 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.32 [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.11 [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 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.0-preview.4.22229.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
arm64 [C:\Program Files\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\arm64\InstallLocation]
x86 [C:\Program Files (x86)\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
Not set

global.json file:
Not found

Learn more:
https://aka.ms/dotnet/info

Download .NET:
https://aka.ms/dotnet/download

Author: Cricle
Assignees: -
Labels:

untriaged, area-NativeAOT-coreclr

Milestone: -

@agocke agocke added this to the 8.0.0 milestone Mar 13, 2023
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Mar 13, 2023
@agocke agocke removed the untriaged New issue has not been triaged by the area owner label Mar 13, 2023
@VSadov
Copy link
Member

VSadov commented Jul 15, 2023

I think I am able to reproduce this. At least I do see abnormal memory consumption on 7.0 with NativeAOT.
The numbers on my machine are not the same as reported, but there is clearly an anomaly that I cannot explain (yet) on NativeAOT.

@VSadov
Copy link
Member

VSadov commented Jul 15, 2023

I am not able to reproduce the problem with 8.0.
Both CoreCLR and NativeAOT stabilize around commit size of 100-150Mb. Considering that this is with Server GC, this is pretty good.

Possibly the bug was fixed or maybe it is still there, but scenario no longer meets conditions for it to appear.
Anyways, - "use 8.0" could be a possible workaround , if that is acceptable.

Still need to figure what is going on on 7.0. (and to be sure it is indeed fixed in 8.0)

@VSadov
Copy link
Member

VSadov commented Jul 15, 2023

With 7.0 I see CoreCLR stabilizing aroung 950 Mb commit size.
That is more than in 8.0 and there could be reasons for that. There were many improvements on the dotnet runtime and on ASP.net sides.

With NativeAOT commit size grows to multiple gigabytes. Sometimes it stabilizes around 2Gb, sometimes it goes to 8Gb and still keeps going. There is something clearly wrong in 7.0

@VSadov
Copy link
Member

VSadov commented Jul 15, 2023

BTW. The app is using 455 threads total. This is on Windows10 machine with 32 logical cores. I assume 64 threads belong to GC and the rest 391 is the app threads.
The number looks a bit high, but not completely unusual.

Same app on CoreCLR uses fewer threads (under 120 total). NativeAOT has a different threadpool on Windows, compared to CoreCLR, so we can't compare directly.

The thread count is stable though, and should not cause memory retention. It is likely that the culprit is somewhere else.

Edit: yes, the app uses roughly the same number of threads on 8.0 (around 430 threads), yet there is no "leak", so that is not the root cause.

@VSadov
Copy link
Member

VSadov commented Jul 15, 2023

@jkotas - some input on how many threads an app can have. (re: #88772 (comment) ).
Here is an ASP.net app that runs fine (on 8.0). When seeing a large number of connections, it may use up to 400 threads on a 32-core machine.

@MichalStrehovsky
Copy link
Member

One big difference on .net 7 is that native aot doesn't use regions. We enabled regions early in 8 but on 7 jit and aot differ in this. Not sure if that could matter but calling it out

@VSadov
Copy link
Member

VSadov commented Jul 17, 2023

One big difference on .net 7 is that native aot doesn't use regions.

This seems unlikely. Regions may help with fragmentation in huge heaps, but unlikely to help with leaks.
I have tried turning off regions, just to be sure. That did not cause the issue to appear in 7.0 CoreCLR or in 8.0 NativeAOT.

@VSadov
Copy link
Member

VSadov commented Jul 17, 2023

When comparing stacks that cause allocations the following stands out:
It is not seen in 8.0, but 7.0 spends noticeable time there. Seeing SyncTable in profiles is pretty rare in general.

Name
|+ brief.api!RhpNewObject
||+ brief.api!S_P_CoreLib_System_Threading_SyncTable__AssignEntry
|||+ brief.api!S_P_CoreLib_System_Threading_Monitor__GetLock
||| + brief.api!S_P_CoreLib_System_Threading_Monitor__Enter_0
|||  + brief.api!Microsoft_Extensions_DependencyInjection_Microsoft_Extensions_DependencyInjection_ServiceLookup_ServiceProviderEngineScope__BeginDispose
|||  |+ brief.api!Microsoft_Extensions_DependencyInjection_Microsoft_Extensions_DependencyInjection_ServiceLookup_ServiceProviderEngineScope__DisposeAsync
|||  | + brief.api!Microsoft_AspNetCore_Http_Microsoft_AspNetCore_Http_Features_RequestServicesFeature__DisposeAsync
|||  |  + brief.api!Microsoft_AspNetCore_Http_Abstractions_Microsoft_AspNetCore_Http_HttpResponse___c____cctor_b__35_1
|||  |   + brief.api!Microsoft_AspNetCore_Server_Kestrel_Core_Microsoft_AspNetCore_Server_Kestrel_Core_Internal_Http_HttpProtocol___FireOnCompleted_g__ProcessEvents_242_0_d__MoveNext
|||  |    + brief.api!S_P_CoreLib_System_Runtime_CompilerServices_AsyncMethodBuilderCore__Start
|||  |     + brief.api!Microsoft_AspNetCore_Server_Kestrel_Core_Microsoft_AspNetCore_Server_Kestrel_Core_Internal_Http_HttpProtocol___FireOnCompleted_g__ProcessEvents_242_0
|||  |      + brief.api!Microsoft_AspNetCore_Server_Kestrel_Core_Microsoft_AspNetCore_Server_Kestrel_Core_Internal_Http_HttpProtocol__ProcessRequests_d__238_1__MoveNext
|||  |       + brief.api!S_P_CoreLib_System_Threading_ExecutionContext__RunFromThreadPoolDispatchLoop
|||  |        + brief.api!S_P_CoreLib_System_Runtime_CompilerServices_AsyncTaskMethodBuilder_1_AsyncStateMachineBox_1>__MoveNext_0
|||  |         + brief.api!S_P_CoreLib_System_Threading_ThreadPoolWorkQueue__Dispatch
|||  |          + brief.api!S_P_CoreLib_System_Threading_ThreadPool__DispatchCallback
|||  |           + ntdll!TppWorkpExecuteCallback
|||  |            + ntdll!TppWorkerThread
|||  |             + kernel32!BaseThreadInitThunk
|||  |              + ntdll!RtlUserThreadStart
|||  |               + AllThreads
|||  |                + Process64 Brief.Api (29992) Args:  --urls http://*:8889

The reson for this allocation is that ServiceProviderEngineScope.BeginDispose takes a lock on its private dictionary object.
NativeAOT in 7.0 did not have thin locks yet, so this means allocating a fat lock for each ServiceProviderEngineScope. (does this happen for each HTTP request?)

The fat locks can in theory add up, but should not leak.

Perhaps finalizer thread cannot keep up with reclaiming so many locks.
(although reclaiming is a fairly cheap operation, at least relative to creating a fat lock, so it should keep up).

@VSadov
Copy link
Member

VSadov commented Jul 17, 2023

A few more bits of info.

  • The 7.0 NativeAOT commit size app actually plateaus at about 8.5 Gb (on my machine). I was just not waiting long enough before. This is not a continuous leak. It stops at some point. More like a very delayed reclamation.
  • In "stable" state the index of the first unused SyncTable entry exceeds 5.3 million. It means that there are points in time when the app has 5.3 million fat locks.

Basically - locks are eventually reclaimed, but it takes long enough time that millions of locks may be necessary to satisfy the demand of the app.

The fat lock itself and its finalizer tracker are very small objects. Millions of locks by itself would not explain consuming gigabytes of memory. However, since the locks are logically attached to something else, they are an indication that some other data structures are not promptly destroyed.

These locks protect instances of:
System.Collections.Generic.Dictionary<DependencyInjection.ServiceLookup.ServiceCacheKey, Object>. It is a dictionary, so probably may contain items and the dictionary is a readonly member of ServiceProviderEngineScope, which, as I can gather from comments, may be cached.
(I am only guessing what this code does, I am not very familiar with DependencyInjection)

I wonder if the cache holds to some sizeable object graphs for too long?

@davidfowl
Copy link
Member

There are 2 main dictionaries in the DI container implementation, the one that stores registrations (global) and one allocated per request to store scoped instances. The latter is likely what you’re seeing. It stores object graphs for scopes instances instantiated by the container.

Interesting to see how expensive that lock is on native AOT

@VSadov
Copy link
Member

VSadov commented Jul 17, 2023

Interestingly, disabling thin locks (so that every lock would allocate a lock object vs. alloc-free use of sync bits), and disabling regions (to rule out regions as a culprit) still does not make the bug to surface on 8.0 NativeAOT.

Runtime churns through a lot of lock objects, but everything seems to be reclaimed orderly and promptly (as expected).
The high watermark for the fat lock count that I see is about 66K, which is basically in a "not a big deal" range and a lot less than 5.3M on 7.0.

@VSadov
Copy link
Member

VSadov commented Jul 17, 2023

Interesting to see how expensive that lock is on native AOT

On 8.0 NativeAOT the cost of a fat lock is the Lock object (3 nints) + DeadEntryCollector (1 int) + dependent handle.
It is not a lot - I'd round it up to 60 bytes or so. CoreCLR would have comparable expenses.

You'd typically need to go out of your way to make locks themselves a problem (before what they are protecting is already a problem).

Also note that neither CoreCLR nor NativeAOT will create lock objects if they can get away with just using sync bits in the object header. Trivial noncontending or microcontending uses of lock statements would not allocate anything.
This part (thin locks) was NYI in 7.0 for NativeAOT

However not having thin locks does not seem the root cause here. Taking out the feature on 8.0 does not cause the "leak" to appear.

@VSadov
Copy link
Member

VSadov commented Jul 17, 2023

Also note that neither CoreCLR nor NativeAOT will create lock objects if they can get away with just using sync bits

For the perspective on this - with thin locks enabled this app has fat lock watermark at 8 on NativeAOT 8.0
And these locks seem to be long-lived as I do not see any recycling of entries in the steady state of the app.

Compared to 66K of simultaneous lock instances needed otherwise, thin locks is a quite impressive optimization.

@VSadov
Copy link
Member

VSadov commented Jul 18, 2023

Ok, all the stuff about locks can be ignored. It is interesting, but not the root cause here.

The actual reason for the "leak" is simply because server GC gets really lazy for this scenario and does not collect very often. I see just one GC happening every few seconds and that sems to be Gen0.

  • There is no "leak" observed if workstation GC is used.
    (as in set DOTNET_gcServer=0)

  • It is also possible to limit the "leak" by forcing GC once in a while.
    Commit size stays within 20-30% above what CoreCLR uses, with something like:

        public static Thread gcForcer = new Thread(
                () =>
                {
                    for (; ; )
                    {
                        // force a GC once a while. It does not need to be blocking.
                        GC.Collect(2, GCCollectionMode.Forced, blocking: false);
                        Thread.Sleep(10000);
                    }
                }
             );

        public static async Task Main(string[] args)
        {
            gcForcer.Start();

In a way GC is not completely wrong here - there is plenty of RAM on the machine and it can afford 8Gb heap, but the expectations are clearly that it collects more often.
I do not think the NativeAOT runtime can change anything to fix this behavior.

I am not sure what changed in 8.0 that this scenario no longer reproduces (regardless of regions turned on or off).
Perhaps GC tunnings have changed or something got fixed?

@mangod9 - I think GC team needs to take a look.

It is also possible that something changed in ASP.net/DependencyInjection and the app no longer meets the pattern that causes GC to be lazy.

Either way - something changed that caused GC to be less lazy and fixed this scenario in 8.0. Porting that change to 7.0, if possible, would be the likely fix.

@ghost
Copy link

ghost commented Jul 19, 2023

Tagging subscribers to this area: @dotnet/gc
See info in area-owners.md if you want to be subscribed.

Issue Details

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

When TFM=.NET7
wrk -t8 -c1000 -d30s --latency http://192.168.2.86:8889 many times, memory will grow to 1G.

When TFM=.NET6
wrk -t8 -c1000 -d30s --latency http://192.168.2.86:8889 many times, memory only 145M.

The Code in program

            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
            var builder = WebApplication.CreateBuilder(args);
            
            builder.Services.AddSingleton(new HitokotoManager(Init()));
            builder.Services.AddResponseCompression();

            var app = builder.Build();

            app.UseResponseCompression();
            app.Use(async (HttpContext builder, RequestDelegate next) =>
            {
                    //Write 1kb data in response
            });

Expected Behavior

Memory will not be much larger due to long concurrent requests

Steps To Reproduce

No response

Exceptions (if any)

No response

.NET Version

7.0.100

Anything else?

.NET SDK:
Version: 7.0.100
Commit: e12b7af219

运行时环境:
OS Name: Windows
OS Version: 10.0.19045
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\7.0.100\

Host:
Version: 7.0.0
Architecture: x64
Commit: d099f07

.NET SDKs installed:
3.1.426 [C:\Program Files\dotnet\sdk]
5.0.303 [C:\Program Files\dotnet\sdk]
5.0.403 [C:\Program Files\dotnet\sdk]
5.0.408 [C:\Program Files\dotnet\sdk]
6.0.112 [C:\Program Files\dotnet\sdk]
6.0.307 [C:\Program Files\dotnet\sdk]
7.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.19 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.31 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.32 [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.11 [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 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.0-preview.4.22251.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.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.19 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.31 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.32 [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.11 [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 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.0-preview.4.22229.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.19 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.30 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.31 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 3.1.32 [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.11 [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 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.12 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.0-preview.4.22229.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
arm64 [C:\Program Files\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\arm64\InstallLocation]
x86 [C:\Program Files (x86)\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
Not set

global.json file:
Not found

Learn more:
https://aka.ms/dotnet/info

Download .NET:
https://aka.ms/dotnet/download

Author: Cricle
Assignees: VSadov
Labels:

area-GC-coreclr

Milestone: 8.0.0

@mangod9 mangod9 modified the milestones: 8.0.0, Future Aug 2, 2023
@sgf
Copy link

sgf commented Aug 9, 2024

On a 1GB RAM cloud Windows OS, the page file is not set. The available memory is about 400MB,
.net AOT Hello World command line App cannot start. Because of its OOM.
The application, does not enter the Main method at all, which causes, in cmd, it does not show any error text/message.

@Cricle
Copy link
Author

Cricle commented Aug 12, 2024

I think this has already been resolved in net8.0. I try to compile AOT with net8.0, and use ab.exe to benchmark. The memory is normal.

@MichalStrehovsky
Copy link
Member

I think this has already been resolved in net8.0. I try to compile AOT with net8.0, and use ab.exe to benchmark. The memory is normal.

Thank you for checking!

@github-actions github-actions bot locked and limited conversation to collaborators Sep 12, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
Archived in project
Development

No branches or pull requests

8 participants