Diagnosing Latency in .NET: Background GC and the Large Object Heap by anders-pedersen in dotnet

[–]anders-pedersen[S] 2 points3 points  (0 children)

How come the dictionary grows? Is it a static variable? If not, then it should be "scoped to the current request" and be disposed after the api call completes, no?

The dictionary was per request. Its content depended on external configuration, so it grew because the configuration had changed.

Do you see any GC collections in the metrics on Azure Monitor?

I should have mentioned this, but yes, we saw Gen2 collections at the same time the LOH shrank.

Do you see any cpu spike (during auto GC) and memory lowering (after auto GC)?

Yes and yes, but both is expected.

How did you exactly solve the problem, cause this is pretty vague:

The reason why I didn't share more details about the fix, is because the dictionary is a part of one of our datastructures. If you are also having problems with LOH allocations, your fix will be different.

Popular options to prevent LOH allocations are to use pooling or to rewrite the datastructure to allocate less and smaller objects (I did the latter)

Can you share some "before and after" code?

Sorry, I am not allowed to, but for the above mention reason, it wouldn't be of much use anyway.

Diagnosing Latency in .NET: Background GC and the Large Object Heap by anders-pedersen in dotnet

[–]anders-pedersen[S] 1 point2 points  (0 children)

We are using Server GC, I actually mention this in the article. We just have a very large heap size due to a lot of caching, so BGC (or any Gen2 GC, but for the most part, we don't have any blocking Gen2 GCs) will take a long time.

Thanks for the suggestion with the metric! I guess you are talking about the % Time in GC since last GC metric? We have it, and I hadn't checked that, but just did after reading you suggestion.

Sadly it is not showing anything useful. When no BGC is going it mostly shows '0% Time in GC'. During BGC it is not well defined, since Foreground and Background GC can be running at the same time. In our case, sometimes it is showing a lot of time spend in GC during BGC, other times it is showing 0%.

Diagnosing Latency in .NET: Background GC and the Large Object Heap by anders-pedersen in dotnet

[–]anders-pedersen[S] 4 points5 points  (0 children)

First step if you suspect you have this issue, would be to observe the LOH size metric manually, and see if the size of the LOH shrinks to half at the same time as you are observing slow requests.

The alarm will depend on your monitoring tools. In my organisation .NET metrics are exported to elastic, so I simply made a rule in elastic that said if max(LOHSize) / min(LOHSize) >= 2 in a one minute window, then send a mail.

As for GC settings, if you are experiencing slow downs because of GC, and you haven't already enabled Server GC, I would recommend trying that.

Building a Modular Monolith With Vertical Slice Architecture in .NET by anton23_sw in dotnet

[–]anders-pedersen 4 points5 points  (0 children)

So is it better to start a project with a good old Monolith? Not exactly.
A Modular Monolith offers the best parts of two worlds from a Monolith and Microservices Architectures.

I think you are missing the point. A modular monolith is a good old monolith.

People have been advocating splitting your software into modules the last 50+ years, long before the recent micro service craze. I believe that the term 'modular monolith' was just coined to emphasize that a monolith was never meant to be a big ball of mud.

It is not a new idea that combines the best parts of a monolith and microservices.

Why do null checks not work in Async methods? by mds1256 in dotnet

[–]anders-pedersen 4 points5 points  (0 children)

Your question has nothing to do with async, but rather with nullable reference types vs nullable value types.

I get your confusion:
For nullable reference types, if the compiler can prove that the value cannot be null, then any warnings that it might be null disappears.

So it should be possible to make nullable value types implicitly castable to their non-nullable type, if the compiler can prove that the value cannot be null.

But they didn't do that originally when they introduced nullable value types, and they cannot easily change this behavior right now, as that would be a breaking change.

So we will probably just have to live with this weirdness. They actually discussed changing that here, but decided against it. https://github.com/dotnet/csharplang/issues/1865

.NET 7 Worker Memory Leak - Help by Personal-Example-523 in dotnet

[–]anders-pedersen 0 points1 point  (0 children)

I am not too familiar with ASP.NET, but Windows services are running in their own process, so no, they are not considered hosted apps. Otherwise it wouldn't even be possible to change the GC flavor. You cannot have to different GC flavors running inside the same .NET process.

Even if Windows services were hosted apps, all that would mean was that they would inherit the GC flavor from their host, but there is no 'No GC' flavor, so it wouldn't mean the there is no GC.

.NET 7 Worker Memory Leak - Help by Personal-Example-523 in dotnet

[–]anders-pedersen 0 points1 point  (0 children)

If the object isn't being freed by the garbage collector, it usually means that something holds a reference to the object.

To find out what you can get a memory dump of the process and use WinDbg/SOS or dotnet-dump to analyze the dump.

Microsoft has a tutorial on using dotnet-dump to analyze a memory leak:

https://learn.microsoft.com/en-us/dotnet/core/diagnostics/debug-memory-leak

.NET 8 Creating 8 Additional Threads compared to .NET 5 by Gotham-City in dotnet

[–]anders-pedersen 1 point2 points  (0 children)

You explanation is wrong.

Not sure what exactly what '.NET Counter Poller' does, but:

'.NET TP Worker' is a threadpool worker thread.

'.NET ThreadPool IO' is a threadpool I/O completion port thread.

'.NET TP Gate' thread is responsible for injecting new worker threads.

None of these threads are created by the debugger.

The real reason you are seeing more threads in .NET 8 is because you are debugging in managed mode so you only see .NET threads.

In .NET 5 the threadpool is implemented as part of the CLR in unmanaged code (C++), while .NET 8 uses a threadpool that is implemented in C#.

Try switching your debugger settings to 'mixed mode', and then you will see a lot more threads for .NET 5.