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 _); } } } } } } }
private void QueueTasks(int parallelism, bool useCollapser, bool sameKey, IKeyStrategy overrideKeyStrategy = null) { IsPolicy policy = GetPolicy(useCollapser, overrideKeyStrategy); for (int i = 0; i < parallelism; i++) { string key = sameKey ? SharedKey : i.ToString(); Context context = new Context(key); ConcurrentTasks[i] = ExecuteThroughPolicy(policy, context, i, true); } }
/// <summary> /// Creates a new <see cref="Polly.Contrib.DuplicateRequestCollapser.AsyncRequestCollapserPolicy{TResult}"/> policy, 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 <TResult> Create(IKeyStrategy keyStrategy, IAsyncLockProvider lockProvider) { if (keyStrategy == null) { throw new ArgumentNullException(nameof(keyStrategy)); } if (lockProvider == null) { throw new ArgumentNullException(nameof(lockProvider)); } return(new AsyncRequestCollapserPolicy <TResult>(keyStrategy, lockProvider)); }
protected abstract IsPolicy GetPolicy(bool useCollapser, IKeyStrategy overrideKeyStrategy = null, ISyncLockProvider lockProvider = null);
protected (int actualExecutions, Task[] tasks) Execute_parallel_delegates_through_policy_with_key_strategy(int parallelism, bool useCollapser, bool sameKey, IKeyStrategy overrideKeyStrategy = null) { ConcurrentTasks = new Task[parallelism]; testOutputHelper.WriteLine("Queueing work."); QueueTasks(parallelism, useCollapser, sameKey, overrideKeyStrategy); testOutputHelper.WriteLine("Waiting for all queued work to start and reach the holding gate."); WaitForAllTasksToBeStarted(parallelism); Thread.Sleep(BlockWaitToCauseBlockingConcurrency); // Release the parallel contention. testOutputHelper.WriteLine("All tasks started. Releasing holding gate."); ReleaseHoldingGate(); // Wait for task completion. Task.WaitAll(ConcurrentTasks); testOutputHelper.WriteLine("All tasks completed."); // Return results to caller; the caller is responsible for asserting. return(ActualExecutions, ConcurrentTasks); }
protected override IsPolicy GetPolicy(bool useCollapser, IKeyStrategy overrideKeyStrategy = null, ISyncLockProvider lockProvider = null) { return(useCollapser ? AsyncRequestCollapserPolicy <ResultClass> .Create(overrideKeyStrategy ?? RequestCollapserPolicy.DefaultKeyStrategy, new AsyncWrapperLockProvider(lockProvider ?? RequestCollapserPolicy.GetDefaultLockProvider())) : (IAsyncPolicy <ResultClass>)Policy.NoOpAsync <ResultClass>()); }
/// <summary> /// Creates a new <see cref="Polly.Contrib.DuplicateRequestCollapser.AsyncRequestCollapserPolicy{TResult}"/> policy, using the supplied <see cref="IKeyStrategy"/> and <see cref="ISyncLockProvider"/> /// </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 <TResult> Create(IKeyStrategy keyStrategy, ISyncLockProvider lockProvider) => Create(keyStrategy, new AsyncWrapperLockProvider(lockProvider));
/// <summary> /// Creates a new <see cref="Polly.Contrib.DuplicateRequestCollapser.AsyncRequestCollapserPolicy{TResult}"/> policy, using the supplied <see cref="IKeyStrategy"/> /// </summary> /// <param name="keyStrategy">A strategy for choosing a key on which to consider requests duplicates.</param> /// <returns>The policy instance.</returns> public static IAsyncRequestCollapserPolicy <TResult> Create(IKeyStrategy keyStrategy) => Create(keyStrategy, AsyncRequestCollapserPolicy.GetDefaultLockProvider());
internal AsyncRequestCollapserPolicy(IKeyStrategy keyStrategy, IAsyncLockProvider lockProvider) { _keyStrategy = keyStrategy ?? throw new ArgumentNullException(nameof(keyStrategy)); _lockProvider = lockProvider ?? throw new ArgumentNullException(nameof(lockProvider)); }
/// <summary> /// Creates a new <see cref="Polly.Contrib.DuplicateRequestCollapser.RequestCollapserPolicy"/> policy, using the supplied <see cref="IKeyStrategy"/> /// </summary> /// <param name="keyStrategy">A strategy for choosing a key on which to consider requests duplicates.</param> /// <returns>The policy instance.</returns> public static ISyncRequestCollapserPolicy Create(IKeyStrategy keyStrategy) => Create(keyStrategy, RequestCollapserPolicy.GetDefaultLockProvider());
protected override IsPolicy GetPolicy(bool useCollapser, IKeyStrategy overrideKeyStrategy = null, ISyncLockProvider lockProvider = null) { return(useCollapser ? RequestCollapserPolicy.Create(overrideKeyStrategy ?? RequestCollapserPolicy.DefaultKeyStrategy, lockProvider ?? RequestCollapserPolicy.GetDefaultLockProvider()) : (ISyncPolicy)Policy.NoOp()); }