Skip to content

Memory Leak on NpgsqlDataSource with password provider #3646

@NikitaCh

Description

@NikitaCh

I've originally opened this issue in npsql repo, but after further research I think the issue is in this package, particularly in the fact that connectionString is being used as a key for cache of DataSource

The issue:

There appears to be a memory leak when using EF + NpgsqlDataSource with password provider.

Versions:
Npgsql.EntityFrameworkCore.PostgreSQL - 9.0.4
Npgsql - 9.0.3

Image Image

Reproducible example attached - MemoryLeak.zip (includes simple docker-compose with postgres for comfort)

Relevant code breakdown:

AppDbContext.cs

internal class AppDbContext : DbContext
{
    public DbSet<SomeModel> SomeModels { get; set; }

    public DbSet<SomeOtherModel> SomeOtherModels { get; set; }

    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<SomeModel>(b =>
        {
            b.HasKey(e => e.Id);
            b.OwnsOne(e => e.JsonModel).ToJson();
        });

        modelBuilder.Entity<SomeOtherModel>(b =>
        {
            b.HasKey(e => e.Id);
        });

        base.OnModelCreating(modelBuilder);
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseNpgsql(@"User ID=postgres;Server=localhost;Port=5433;Database=postgres_db;Pooling=true;", sqlOptions =>
        {
            sqlOptions.EnableRetryOnFailure(maxRetryCount: 3);
            sqlOptions.ConfigureDataSource(dsBuilder =>
            {
                dsBuilder.ConnectionStringBuilder.Host = "localhost";
                dsBuilder.Name = "postgres_db";
                dsBuilder.UsePasswordProvider(_ => Guid.NewGuid().ToString(), (_, _) => ValueTask.FromResult(Guid.NewGuid().ToString()));
            });
        });
        optionsBuilder.UseSnakeCaseNamingConvention();
        base.OnConfiguring(optionsBuilder);
    }
}

Program.cs

using MemoryLeak;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddDbContext<AppDbContext>();

var app = builder.Build();

for(var i = 0; i < 1_000_000; ++i)
{
    using var scope = app.Services.CreateScope();
    var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    await dbContext.Database.CanConnectAsync();

    if (i % 100 == 0)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        Console.WriteLine($"Iteration: {i}");
        await Task.Delay(3000);
    }
}

From what I can understand, it seems like there is some cache based on ConnectionString, giving the fact that password provider changes the connection string (in this example all the time) - the cache grows indefinitely.

As soon as you change the password provider to return some constant string - everything goes to normal
(For example - dsBuilder.UsePasswordProvider(_ => "constant", (_, _) => ValueTask.FromResult("string"));) :
Image

This is a critical issue, because it is a requirement for a lot of applications to use for example IAM Authorization to connect to RDS - hence use of RDSAuthTokenGenerator for password generation. The application memory grows "indefinitely" in this scenario. The rate of growth seems to be dependent on model size.

Thanks for the help.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions