public async ValueTask <Dictionary <IDatabase, Task <bool> >?> TryAcquireAsync() { this._cancellationToken.ThrowIfCancellationRequested(); var isSynchronous = SyncViaAsync.IsSynchronous; if (isSynchronous && this._databases.Count == 1) { return(this.TrySingleFullySynchronousAcquire()); } var primitive = this._primitive; var tryAcquireTasks = this._databases.ToDictionary( db => db, db => Helpers.SafeCreateTask(state => state.primitive.TryAcquireAsync(state.db), (primitive, db)) ); var waitForAcquireTask = this.WaitForAcquireAsync(tryAcquireTasks); var succeeded = false; try { succeeded = await waitForAcquireTask.AwaitSyncOverAsync().ConfigureAwait(false); } finally { // clean up if (!succeeded) { List <Task>?releaseTasks = null; foreach (var kvp in tryAcquireTasks) { // if the task hasn't finished yet, we don't want to do any releasing now; just // queue a release command to run when the task eventually completes if (!kvp.Value.IsCompleted) { RedLockHelper.FireAndForgetReleaseUponCompletion(primitive, kvp.Key, kvp.Value); } // otherwise, unless we know we failed to acquire, do a release else if (!RedLockHelper.ReturnedFalse(kvp.Value)) { if (isSynchronous) { try { primitive.Release(kvp.Key, fireAndForget: true); } catch { } } else { (releaseTasks ??= new List <Task>()) .Add(Helpers.SafeCreateTask(state => state.primitive.ReleaseAsync(state.Key, fireAndForget: true), (primitive, kvp.Key))); } } } if (releaseTasks != null) { await Task.WhenAll(releaseTasks).ConfigureAwait(false); } } } return(succeeded ? tryAcquireTasks : null); }
public async ValueTask ReleaseAsync() { var isSynchronous = SyncViaAsync.IsSynchronous; var unreleasedTryAcquireOrRenewTasks = this._tryAcquireOrRenewTasks.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); List <Exception>?releaseExceptions = null; var successCount = 0; var faultCount = 0; var databaseCount = unreleasedTryAcquireOrRenewTasks.Count; try { while (true) { var releaseableDatabases = unreleasedTryAcquireOrRenewTasks.Where(kvp => kvp.Value.IsCompleted) // work through non-faulted tasks first .OrderByDescending(kvp => kvp.Value.Status == TaskStatus.RanToCompletion) // then start with failed since no action is required to release those .ThenBy(kvp => kvp.Value.Status == TaskStatus.RanToCompletion && kvp.Value.Result) .Select(kvp => kvp.Key) .ToArray(); foreach (var db in releaseableDatabases) { var tryAcquireOrRenewTask = unreleasedTryAcquireOrRenewTasks[db]; unreleasedTryAcquireOrRenewTasks.Remove(db); if (RedLockHelper.ReturnedFalse(tryAcquireOrRenewTask)) { // if we failed to acquire, we don't need to release ++successCount; } else { try { if (isSynchronous) { this._primitive.Release(db, fireAndForget: false); } else { await this._primitive.ReleaseAsync(db, fireAndForget : false).ConfigureAwait(false); } ++successCount; } catch (Exception ex) { (releaseExceptions ??= new List <Exception>()).Add(ex); ++faultCount; if (RedLockHelper.HasTooManyFailuresOrFaults(faultCount, databaseCount)) { throw new AggregateException(releaseExceptions !).Flatten(); } } } if (RedLockHelper.HasSufficientSuccesses(successCount, databaseCount)) { return; } } // if we haven't released enough yet to be done or certain of success or failure, wait for another to finish if (isSynchronous) { Task.WaitAny(unreleasedTryAcquireOrRenewTasks.Values.ToArray()); } else { await Task.WhenAny(unreleasedTryAcquireOrRenewTasks.Values).ConfigureAwait(false); } } } finally // fire and forget the rest { foreach (var kvp in unreleasedTryAcquireOrRenewTasks) { RedLockHelper.FireAndForgetReleaseUponCompletion(this._primitive, kvp.Key, kvp.Value); } } }