Exemplo n.º 1
0
        internal static async Task <TResult> ImplementationAsync <TResult>(
            Func <Context, CancellationToken, Task <TResult> > action,
            Context context,
            CancellationToken cancellationToken,
            bool continueOnCapturedContext,
            IKeyStrategy keyStrategy,
            ConcurrentDictionary <string, Lazy <Task <object> > > collapser,
            IAsyncLockProvider lockProvider)
        {
            cancellationToken.ThrowIfCancellationRequested();

            string key = keyStrategy.GetKey(context);

            // Fast-path if no key specified on Context (similar to CachePolicy).
            if (key == null)
            {
                return(await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext));
            }

            Lazy <Task <object> > lazy;

            await using (lockProvider.AcquireLockAsync(key, context, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext))
            {
                lazy = collapser.GetOrAdd(key, new Lazy <Task <object> >(async() => await action(context, cancellationToken).ConfigureAwait(continueOnCapturedContext), LazyThreadSafetyMode.ExecutionAndPublication)); // Note: per documentation, LazyThreadSafetyMode.ExecutionAndPublication guarantees single execution, but means the executed code must not lock, as this risks deadlocks.  We should document.
            }

            try
            {
                return((TResult)await lazy.Value.ConfigureAwait(continueOnCapturedContext));
            }
            finally
            {
                // As soon as the lazy has returned a result to one thread, the concurrent request set is over, so we evict the lazy from the ConcurrentDictionary.
                // We need to evict within a lock, to be sure we are not, due to potential race with new threads populating, evicting a different lazy created by a different thread.
                // To reduce lock contention, first check outside the lock whether we still need to remove it (we will double-check inside the lock).
                if (collapser.TryGetValue(key, out Lazy <Task <object> > currentValue))
                {
                    if (currentValue == lazy)
                    {
                        await using (lockProvider.AcquireLockAsync(key, context, cancellationToken, continueOnCapturedContext)
                                     .ConfigureAwait(continueOnCapturedContext))
                        {
                            // Double-check that there has not been a race which updated the dictionary with a new value.
                            if (collapser.TryGetValue(key, out Lazy <Task <object> > valueWithinLock))
                            {
                                if (valueWithinLock == lazy)
                                {
                                    collapser.TryRemove(key, out _);
                                }
                            }
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Creates a new <see cref="Polly.Contrib.DuplicateRequestCollapser.AsyncRequestCollapserPolicy"/>, using the supplied <see cref="IKeyStrategy"/> and <see cref="IAsyncLockProvider"/>
        /// </summary>
        /// <param name="keyStrategy">A strategy for choosing a key on which to consider requests duplicates.</param>
        /// <param name="lockProvider">The lock provider.</param>
        /// <returns>The policy instance.</returns>
        public static IAsyncRequestCollapserPolicy Create(IKeyStrategy keyStrategy, IAsyncLockProvider lockProvider)
        {
            if (keyStrategy == null)
            {
                throw new ArgumentNullException(nameof(keyStrategy));
            }
            if (lockProvider == null)
            {
                throw new ArgumentNullException(nameof(lockProvider));
            }

            return(new AsyncRequestCollapserPolicy(keyStrategy, lockProvider));
        }
Exemplo n.º 3
0
 /// <summary>
 /// Creates a new <see cref="Polly.Contrib.DuplicateRequestCollapser.AsyncRequestCollapserPolicy{TResult}"/> policy, using the supplied <see cref="IAsyncLockProvider"/>
 /// </summary>
 /// <param name="lockProvider">The lock provider.</param>
 /// <returns>The policy instance.</returns>
 public static IAsyncRequestCollapserPolicy <TResult> Create(IAsyncLockProvider lockProvider)
 => Create(RequestCollapserPolicy.DefaultKeyStrategy, lockProvider);
 internal AsyncRequestCollapserPolicy(IKeyStrategy keyStrategy, IAsyncLockProvider lockProvider)
 {
     _keyStrategy  = keyStrategy ?? throw new ArgumentNullException(nameof(keyStrategy));
     _lockProvider = lockProvider ?? throw new ArgumentNullException(nameof(lockProvider));
 }