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);
        }
Example #2
0
        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);
                }
            }
        }