question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Memory leak in .NET 6 when adding a type resolver repeatedly

See original GitHub issue

Hi everyone,

After migration to .NET 6 I’ve noticed a high CPU usage which causing by memory pressure as GC runs more aggressively.

Analysing the coredump i’ve noticed a lot of Npgsql.NetTopologySuite.Internal.NetTopologySuiteTypeHandlerResolverFactory objects stays in Gen 2, digging deeper i’ve found the GC root of those ones which is basically looks like this:

HandleTable:
    00007FB6DB481378 (strong handle)
    -> 00007FB5CC007C10 System.Object[]
    -> 00007FB50C0C99C0 Npgsql.TypeMapping.GlobalTypeMapper
    -> 00007FB50C0C9A00 System.Collections.Generic.List`1[[Npgsql.Internal.TypeHandling.TypeHandlerResolverFactory, Npgsql]]
    -> 00007FB58C11F150 Npgsql.Internal.TypeHandling.TypeHandlerResolverFactory[]
    -> 00007FB52FED6F98 Npgsql.NetTopologySuite.Internal.NetTopologySuiteTypeHandlerResolverFactory

Reproduce code is pretty simple:

  1. Create console app with Npsql provider inside
  2. Create sample DbContext and add it in startup
  3. Register NetTopologySuite using extension .UseNetTopologySuite()
  4. in Program.cs build the Host, create an infinite loop which then just create the scope, and resolves DbContext
  5. Run code, and see how memory grows.

Full example: Startup.cs

services.AddDbContext<AppDbContext>(options =>
{
    options.UseNpgsql("connectionstring", o => o.UseNetTopologySuite());
});

Program.cs

var host = CreateHostBuilder(args).Build();
while (true)
{
    using var scope = host.Services.CreateScope();
    scope.ServiceProvider.GetRequiredService<AppDbContext>();
}

Now let me deeply explain what’s wrong here:

  1. By default .AddDbContext() register DbContext with scoped lifetime, which is intentional and nothing wrong here.
  2. Each time DbContext is needed - it’s resolved from SeviceProvider
  3. In order to resolve the service, it should call provided serviceFactory func to configure the DbContext options so each time DbContext is resolved, registration func is called for each particular ServiceScope
  4. Regisration func in our example is (options) => options.UseNpgsql("connectionstring", o => o.UseNetTopologySuite());
  5. .UseNetTopologySuite() extension calls the NpgsqlConnection.GlobalTypeMapper.UseNetTopologySuite(parameters) extension over GlobalTypeMapper static singletone instance which is basically TypeMapping.GlobalTypeMapper.Instance inside
  6. The extension above calls mapper.AddTypeResolverFactory(new NetTopologySuiteTypeHandlerResolverFactory(coordinateSequenceFactory, precisionModel, handleOrdinates, geographyAsDefault))
  7. TypeMapping.GlobalTypeMapper.AddTypeResolverFactory() listed here just inserts the newly created factory into internal list.
  8. List - which is holding the factories is a property of static instance so it will continue referencing of all those created factories forever, adding new instance each time - scope is created and DbContext instance is resolved.

This issue is critical as it will affect all users under normal workloads with scoped DbContext (which is default) and using NetTopologySuite.

P.S. I can submit the PR fixing this if you have no time, but let’s agree on the proper place to fix this issue.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:1
  • Comments:9 (4 by maintainers)

github_iconTop GitHub Comments

4reactions
rojicommented, Nov 24, 2021

Good to hear, thanks and sorry that such a critical bug slipped into the release. We’ll release 6.0.1 soon, until then please continue using this patch build.

2reactions
rojicommented, Dec 2, 2021

@thecodemonk great to hear, thanks! Yes, we definitely intend to release 6.0.1 within the next few days (working on some last fixes that need to get in). Please feel free to use the CI release in the meantime.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Find, Fix, and Avoid Memory Leaks in C# .NET: 8 Best ...
6. Use the Dispose pattern to prevent unmanaged memory leaks. Your .NET application constantly uses unmanaged resources. The .NET framework ...
Read more >
Why EF core memory leak exception is solved by static ...
This can potentially lead to a memory leak if the query is executed repeatedly, because the expression tree and the Person objects it ......
Read more >
Debug a memory leak in .NET Core
A memory leak may happen when your app references objects that it no longer needs to perform the desired task. Referencing said objects...
Read more >
Fixing a memory leak in .Net Core - A Method to Madness
The memory leak becomes quite obvious : the memory consumption keeps on growing, until it reaches a threshold matching the server's specs. We ......
Read more >
Unmanaged memory leaks in .NET
In either case, the first task is to detect which kind of memory is leaking and investigate the problem memory domain in more...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found