Skip to content

ReleaseAsync on Postgres (Connection is not open) #263

@r-ising

Description

@r-ising

Hi,

Thank you for this great library.

We are encountering a System.InvalidOperationException: Connection is not open error in our production environment.
We see this error about 100-200 times per day under load.

Environment:

  • Library Version: latest
  • Npgsql Version: 9.0.4 and 10.0.1
  • Framework: .NET 9 and .NET 10
  • Database: PostgreSQL 18

Reproduction:

I tried to reproduce the issue locally using a PostgreSQL Docker container (with increased connection limit: 999) and the following console application. The issue seems to appear under high concurrency.

using Medallion.Threading;
using Medallion.Threading.Postgres;

var list = Enumerable.Range(1, 200).ToList();
var tasks = new List<Task>();
var error = new List<Exception>();
IDistributedLockProvider synchronizationProvider =
	new PostgresDistributedSynchronizationProvider(
		"User ID=xxxx;Password=xxxx;Database=xxxx;Host=127.0.0.1;Port=5432;Pooling=true");
for (var j = 0; j < 30; j++)
{
	foreach (var id in list)
	{
		var x = Task.Run(
			async () =>
			{
				try
				{
					var timeout = TimeSpan.FromMinutes(5);
					var key = "X" + id;
					await using var writeLock =
						await synchronizationProvider.CreateLock(key)
							.AcquireAsync(timeout);

					await Task.Delay(
						Random.Shared.Next(1, 10),
						writeLock.HandleLostToken);
				}
				catch (Exception ex)
				{
					Console.WriteLine(ex);
					Console.WriteLine(ex.StackTrace);
					Environment.Exit(1);
				}
			});
		tasks.Add(x);
	}

	await Task.WhenAll(tasks);
}

Console.WriteLine("Task completed");

Stack Trace:

System.InvalidOperationException: Connection is not open
   at Npgsql.ThrowHelper.ThrowInvalidOperationException(String message)
   at Npgsql.NpgsqlCommand.CheckAndGetConnection()
   at Npgsql.NpgsqlCommand.Prepare(Boolean async, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.PrepareAsync(CancellationToken cancellationToken)
   at Medallion.Threading.Internal.Data.DatabaseCommand.PrepareIfNeededAsync(CancellationToken cancellationToken) in /_/src/DistributedLock.Core/Internal/Data/DatabaseCommand.cs:line 182
   at Medallion.Threading.Internal.Data.DatabaseCommand.ExecuteAsync[TResult](Func`3 executeAsync, Func`2 executeSync, CancellationToken cancellationToken, Boolean disallowAsyncCancellation, Boolean isConnectionMonitoringQuery) in /_/src/DistributedLock.Core/Internal/Data/DatabaseCommand.cs:line 81
   at Medallion.Threading.Postgres.PostgresAdvisoryLock.ReleaseAsync(DatabaseConnection connection, PostgresAdvisoryLockKey key, Boolean isTry) in /_/src/DistributedLock.Postgres/PostgresAdvisoryLock.cs:line 278
   at Medallion.Threading.Internal.Data.MultiplexedConnectionLock.ReleaseAsync[TLockCookie](IDbSynchronizationStrategy`1 strategy, String name, TLockCookie lockCookie) in /_/src/DistributedLock.Core/Internal/Data/MultiplexedConnectionLock.cs:line 154
   at Medallion.Threading.Internal.Data.MultiplexedConnectionLock.ReleaseAsync[TLockCookie](IDbSynchronizationStrategy`1 strategy, String name, TLockCookie lockCookie) in /_/src/DistributedLock.Core/Internal/Data/MultiplexedConnectionLock.cs:line 168
....

It looks like the connection might be closed or returned to the pool unexpectedly while the lock is trying to release or prepare a command.

Do you have any idea how to fix this or what might be causing the race condition?

Thanks

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions