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.
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 downSee this minimal repro which shows that connection 2 waits 5 seconds (the configured timeout) even though the data source has already been disposed: