Skip to content

Memory Leak on NpgsqlDataSource with password provider #6272

@NikitaCh

Description

@NikitaCh

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

Opening it here because it seems to be the issue directly with NpgsqlDataSource, likely with MetricReporter

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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions