public async ValueTask <IDistributedSynchronizationHandle?> TryAcquireAsync <TLockCookie>( TimeoutValue timeout, IDbSynchronizationStrategy <TLockCookie> strategy, CancellationToken cancellationToken, IDistributedSynchronizationHandle?contextHandle) where TLockCookie : class { IDistributedSynchronizationHandle?result = null; IAsyncDisposable?connectionResource = null; try { DatabaseConnection connection; if (contextHandle != null) { connection = GetContextHandleConnection <TLockCookie>(contextHandle); } else { connectionResource = connection = this._connectionFactory(); if (connection.IsExernallyOwned) { if (!connection.CanExecuteQueries) { throw new InvalidOperationException("The connection and/or transaction are disposed or closed"); } } else { await connection.OpenAsync(cancellationToken).ConfigureAwait(false); if (this._transactionScopedIfPossible) // for an internally-owned connection, we must create the transaction { await connection.BeginTransactionAsync().ConfigureAwait(false); } } } var lockCookie = await strategy.TryAcquireAsync(connection, this._name, timeout, cancellationToken).ConfigureAwait(false); if (lockCookie != null) { result = new Handle <TLockCookie>(connection, strategy, this._name, lockCookie, transactionScoped: this._transactionScopedIfPossible && connection.HasTransaction, connectionResource); if (!this._keepaliveCadence.IsInfinite) { connection.SetKeepaliveCadence(this._keepaliveCadence); } } } finally { // if we fail to acquire or throw, make sure to clean up the connection if (result == null && connectionResource != null) { await connectionResource.DisposeAsync().ConfigureAwait(false); } } return(result); }
public async ValueTask <Result> TryAcquireAsync <TLockCookie>( string name, TimeoutValue timeout, IDbSynchronizationStrategy <TLockCookie> strategy, TimeoutValue keepaliveCadence, CancellationToken cancellationToken, bool opportunistic) where TLockCookie : class { using var mutextHandle = await this._mutex.TryAcquireAsync(opportunistic?TimeSpan.Zero : Timeout.InfiniteTimeSpan, cancellationToken).ConfigureAwait(false); if (mutextHandle == null) { // mutex wasn't free, so just give up Invariant.Require(opportunistic); // The current lock is busy so we allow retry but on a different lock instance. We can't safely dispose // since we never acquired the mutex so we can't check _heldLocks return(new Result(MultiplexedConnectionLockRetry.Retry, canSafelyDispose: false)); } try { if (this._heldLocksToKeepaliveCadences.ContainsKey(name)) { // we won't try to hold the same lock twice on one connection. At some point, we could // support this case in-memory using a counter for each multiply-held lock name and being careful // with modes return(this.GetFailureResultNoLock(isAlreadyHeld: true, opportunistic, timeout)); } if (!this._connection.CanExecuteQueries) { await this._connection.OpenAsync(cancellationToken).ConfigureAwait(false); } var lockCookie = await strategy.TryAcquireAsync(this._connection, name, opportunistic?TimeSpan.Zero : timeout, cancellationToken).ConfigureAwait(false); if (lockCookie != null) { var handle = new ManagedFinalizationDistributedLockHandle(new Handle <TLockCookie>(this, strategy, name, lockCookie)); this._heldLocksToKeepaliveCadences.Add(name, keepaliveCadence); if (!keepaliveCadence.IsInfinite) { this.SetKeepaliveCadenceNoLock(); } return(new Result(handle)); } // we failed to acquire the lock, so we should retry if we were being opportunistic and artificially // shortened the timeout return(this.GetFailureResultNoLock(isAlreadyHeld: false, opportunistic, timeout)); } finally { await this.CloseConnectionIfNeededNoLockAsync().ConfigureAwait(false); } }
public Handle( DatabaseConnection connection, IDbSynchronizationStrategy <TLockCookie> strategy, string name, TLockCookie lockCookie, bool transactionScoped, IAsyncDisposable?connectionResource) { this._innerHandle = new InnerHandle(connection, strategy, name, lockCookie, transactionScoped, connectionResource); // we don't do managed finalization for externally-owned connections/transactions since it might violate thread-safety // on those objects (we don't know when they're in use) this._finalizer = connection.IsExernallyOwned ? null : ManagedFinalizerQueue.Instance.Register(this, this._innerHandle); }
public InnerHandle( DatabaseConnection connection, IDbSynchronizationStrategy <TLockCookie> strategy, string name, TLockCookie lockCookie, bool scopedToOwnTransaction, IAsyncDisposable?connectionResource) { this.Connection = connection; this._strategy = strategy; this._name = name; this._lockCookie = lockCookie; this._scopedToOwnedTransaction = scopedToOwnTransaction; this._connectionResource = connectionResource; }
public ValueTask <IDistributedSynchronizationHandle?> TryAcquireAsync <TLockCookie>( TimeoutValue timeout, IDbSynchronizationStrategy <TLockCookie> strategy, CancellationToken cancellationToken, IDistributedSynchronizationHandle?contextHandle) where TLockCookie : class { // cannot multiplex for updates, since we cannot predict whether or not there will be a request to elevate // to an exclusive lock which asks for a long timeout if (!strategy.IsUpgradeable && contextHandle == null) { return(this._multiplexedConnectionLockPool.TryAcquireAsync(this._connectionString, this._name, timeout, strategy, keepaliveCadence: this._keepaliveCadence, cancellationToken)); } // otherwise, fall back to our fallback lock return(this._fallbackLock.TryAcquireAsync(timeout, strategy, cancellationToken, contextHandle)); }
private async ValueTask ReleaseAsync <TLockCookie>(IDbSynchronizationStrategy <TLockCookie> strategy, string name, TLockCookie lockCookie) where TLockCookie : class { using var _ = await this._mutex.AcquireAsync(CancellationToken.None).ConfigureAwait(false); try { await strategy.ReleaseAsync(this._connection, name, lockCookie).ConfigureAwait(false); } finally { if (this._heldLocksToKeepaliveCadences.TryGetValue(name, out var keepaliveCadence)) { this._heldLocksToKeepaliveCadences.Remove(name); if (!keepaliveCadence.IsInfinite) { // note: we do this even if we're about to close the connection because we'll want // the correct cadence set when and if we re-open this.SetKeepaliveCadenceNoLock(); } } await this.CloseConnectionIfNeededNoLockAsync().ConfigureAwait(false); } }
public Handle(MultiplexedConnectionLock @lock, IDbSynchronizationStrategy <TLockCookie> strategy, string name, TLockCookie lockCookie) { this._name = name; this._box = RefBox.Create((@lock, strategy, lockCookie, default(IDatabaseConnectionMonitoringHandle))); }
public async ValueTask <IDistributedSynchronizationHandle?> TryAcquireAsync <TLockCookie>( string connectionString, string name, TimeoutValue timeout, IDbSynchronizationStrategy <TLockCookie> strategy, TimeoutValue keepaliveCadence, CancellationToken cancellationToken) where TLockCookie : class { // opportunistic phase: see if we can use a connection that is already holding a lock // to acquire the current lock var existingLock = await this.GetExistingLockOrDefaultAsync(connectionString).ConfigureAwait(false); if (existingLock != null) { var canSafelyDisposeExistingLock = false; try { var opportunisticResult = await TryAcquireAsync(existingLock, opportunistic : true).ConfigureAwait(false); if (opportunisticResult.Handle != null) { return(opportunisticResult.Handle); } // this will always be false if handle is non-null, so we can set if afterwards canSafelyDisposeExistingLock = opportunisticResult.CanSafelyDispose; switch (opportunisticResult.Retry) { case MultiplexedConnectionLockRetry.NoRetry: return(null); case MultiplexedConnectionLockRetry.RetryOnThisLock: var retryOnThisLockResult = await TryAcquireAsync(existingLock, opportunistic : false).ConfigureAwait(false); canSafelyDisposeExistingLock = retryOnThisLockResult.CanSafelyDispose; return(retryOnThisLockResult.Handle); case MultiplexedConnectionLockRetry.Retry: break; default: throw new InvalidOperationException("unexpected retry"); } } finally { // since we took this lock from the pool, always return it to the pool await this.StoreOrDisposeLockAsync(connectionString, existingLock, shouldDispose : canSafelyDisposeExistingLock).ConfigureAwait(false); } } // normal phase: if we were not able to be opportunistic, ensure that we have a lock var @lock = new MultiplexedConnectionLock(this.ConnectionFactory(connectionString)); MultiplexedConnectionLock.Result?result = null; try { result = await TryAcquireAsync(@lock, opportunistic : false).ConfigureAwait(false); Invariant.Require(result !.Value.Retry == MultiplexedConnectionLockRetry.NoRetry, "Acquire on fresh lock should not recommend a retry"); } finally { // if we failed to even acquire a result on a brand new lock, then there's definitely no reason to store it await this.StoreOrDisposeLockAsync(connectionString, @lock, shouldDispose : result?.CanSafelyDispose ?? true).ConfigureAwait(false); } return(result.Value.Handle); ValueTask <MultiplexedConnectionLock.Result> TryAcquireAsync(MultiplexedConnectionLock @lock, bool opportunistic) => @lock.TryAcquireAsync(name, timeout, strategy, keepaliveCadence, cancellationToken, opportunistic); }