Skip to content

Disposing a pooled data source does not release pending connection opens #6514

@brianpursley

Description

@brianpursley

When a pooled NpgsqlDataSource is exhausted and an Open/OpenAsync call is blocked waiting for a connector, disposing the data source does not release that pending open attempt.

Instead, the pending call continues waiting until the pool timeout elapses, and then fails with the normal pool-exhaustion timeout error:
"The connection pool has been exhausted..."

This is misleading because the real cause is that the data source has already been disposed. It also means blocked callers can remain stuck for the full pool timeout during shutdown.

Expected behavior is for pending open attempts to fail promptly when the data source is disposed, ideally with the exception: The connection pool has been shut down

See this minimal repro which shows that connection 2 waits 5 seconds (the configured timeout) even though the data source has already been disposed:

using System.Diagnostics;
using Npgsql;

var csb = new NpgsqlConnectionStringBuilder("Host=localhost;Database=npgsql_tests;Username=postgres;Password=postgres;")
{
    MaxPoolSize = 1,
    Timeout = 5
};
var dataSource = NpgsqlDataSource.Create(csb);

var sw = Stopwatch.StartNew();

await using var connection1 = await OpenConnectionAsync(1);

// Connection pool is exhausted, so opening a second connection will block
var pending = OpenConnectionAsync(2);

Console.WriteLine($"[{sw.ElapsedMilliseconds}] Disposing data source...");
await dataSource.DisposeAsync().ConfigureAwait(false);
Console.WriteLine($"[{sw.ElapsedMilliseconds}] Disposed data source.");

// Connection 2 is still trying to connect until it eventually times out
try { await using var connection2 = await pending; } catch { /* ignored */ }

return;

async Task<NpgsqlConnection> OpenConnectionAsync(int connectionNumber)
{
    try
    {
        Console.WriteLine($"[{sw.ElapsedMilliseconds}] Opening connection {connectionNumber}...");
        var connection = await dataSource.OpenConnectionAsync().ConfigureAwait(false);
        Console.WriteLine($"[{sw.ElapsedMilliseconds}] Connection {connectionNumber} opened");
        return connection;
    }
    catch (Exception ex)
    {
        Console.Error.WriteLine($"[{sw.ElapsedMilliseconds}] Failed to open connection {connectionNumber}: {ex.Message}");
        throw;
    }
}
[0] Opening connection 1...
[86] Connection 1 opened
[86] Opening connection 2...
[87] Disposing data source...
[89] Disposed data source.
[5090] Failed to open connection 2: The connection pool has been exhausted, either raise 'Max Pool Size' (currently 1) or 'Timeout' (currently 5 seconds) in your connection string.

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