using Medallion.Threading.Internal; using Medallion.Threading.Internal.Data; using System.Data; #if NET7_0_OR_GREATER using System.Data.Common; #endif namespace Medallion.Threading.Postgres; /// /// Implements a distributed lock using Postgres advisory locks /// (see https://www.postgresql.org/docs/12/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS) /// public sealed partial class PostgresDistributedLock : IInternalDistributedLock { private readonly IDbDistributedLock _internalLock; /// /// Constructs a lock with the given (effectively the lock name), , /// and /// public PostgresDistributedLock(PostgresAdvisoryLockKey key, string connectionString, Action? options = null) : this(key, CreateInternalLock(key, connectionString, options)) { } /// /// Constructs a lock with the given (effectively the lock name) and . /// public PostgresDistributedLock(PostgresAdvisoryLockKey key, IDbConnection connection) : this(key, CreateInternalLock(key, connection)) { } #if NET7_0_OR_GREATER /// /// Constructs a lock with the given (effectively the lock name) and , /// and . /// /// Not compatible with connection multiplexing. /// public PostgresDistributedLock(PostgresAdvisoryLockKey key, DbDataSource dbDataSource, Action? options = null) : this(key, CreateInternalLock(key, dbDataSource, options)) { } #endif private PostgresDistributedLock(PostgresAdvisoryLockKey key, IDbDistributedLock internalLock) { this.Key = key; this._internalLock = internalLock; } /// /// The that uniquely identifies the lock on the database /// public PostgresAdvisoryLockKey Key { get; } string IDistributedLock.Name => this.Key.ToString(); ValueTask IInternalDistributedLock.InternalTryAcquireAsync(TimeoutValue timeout, CancellationToken cancellationToken) => this._internalLock.TryAcquireAsync(timeout, PostgresAdvisoryLock.ExclusiveLock, cancellationToken, contextHandle: null).Wrap(h => new PostgresDistributedLockHandle(h)); internal static IDbDistributedLock CreateInternalLock(PostgresAdvisoryLockKey key, string connectionString, Action? options) { if (connectionString == null) { throw new ArgumentNullException(nameof(connectionString)); } var (keepaliveCadence, useTransaction, useMultiplexing) = PostgresConnectionOptionsBuilder.GetOptions(options); return useMultiplexing ? new OptimisticConnectionMultiplexingDbDistributedLock(key.ToString(), connectionString, PostgresMultiplexedConnectionLockPool.Instance, keepaliveCadence) : new DedicatedConnectionOrTransactionDbDistributedLock(key.ToString(), () => new PostgresDatabaseConnection(connectionString), useTransaction: useTransaction, keepaliveCadence); } internal static IDbDistributedLock CreateInternalLock(PostgresAdvisoryLockKey key, IDbConnection connection) { if (connection == null) { throw new ArgumentNullException(nameof(connection)); } return new DedicatedConnectionOrTransactionDbDistributedLock(key.ToString(), () => new PostgresDatabaseConnection(connection)); } #if NET7_0_OR_GREATER internal static IDbDistributedLock CreateInternalLock(PostgresAdvisoryLockKey key, DbDataSource dbDataSource, Action? options) { if (dbDataSource == null) { throw new ArgumentNullException(nameof(dbDataSource)); } // Multiplexing is currently incompatible with DbDataSource (see #238), so default it to false var originalOptions = options; options = o => { o.UseMultiplexing(false); originalOptions?.Invoke(o); }; var (keepaliveCadence, useTransaction, useMultiplexing) = PostgresConnectionOptionsBuilder.GetOptions(options); return useMultiplexing ? throw new NotSupportedException("Multiplexing is current incompatible with DbDataSource.") : new DedicatedConnectionOrTransactionDbDistributedLock(key.ToString(), () => new PostgresDatabaseConnection(dbDataSource), useTransaction: useTransaction, keepaliveCadence); } #endif }