Skip to content

Commit 13d124a

Browse files
martincostellopentp
andcommitted
Performance tweaks
- Avoid LINQ to create components. - Do not recreate `Outcome<T>` for chaos. - Avoid predicate allocation. - Use `ArgumentNullException.ThrowIfNull()` where possible. - Add throw helper for `ObjectDisposedException`. - Remove unused `Stopwatch`. - Use `Volatile.Read` instead of `Interlocked.CompareExchange()`. Cherry-picked from #2664. Co-Authored-By: Pent Ploompuu <[email protected]>
1 parent 0459a8b commit 13d124a

File tree

6 files changed

+13
-18
lines changed

6 files changed

+13
-18
lines changed

src/Polly.Core/ResiliencePipelineBuilderBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ internal PipelineComponent BuildPipelineComponent()
121121

122122
_used = true;
123123

124-
var components = _entries.Select(CreateComponent).ToList();
124+
var components = _entries.ConvertAll(CreateComponent);
125125

126126
if (components.Count == 0)
127127
{

src/Polly.Core/Simmy/Outcomes/ChaosOutcomeStrategy.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ protected internal override async ValueTask<Outcome<T>> ExecuteCore<TState>(Func
2020
{
2121
try
2222
{
23-
if (await ShouldInjectAsync(context).ConfigureAwait(context.ContinueOnCapturedContext)
24-
&& await _outcomeGenerator(new(context)).ConfigureAwait(context.ContinueOnCapturedContext) is Outcome<T> outcome)
23+
if (await ShouldInjectAsync(context).ConfigureAwait(context.ContinueOnCapturedContext) &&
24+
await _outcomeGenerator(new(context)).ConfigureAwait(context.ContinueOnCapturedContext) is Outcome<T> outcome)
2525
{
2626
var args = new OnOutcomeInjectedArguments<T>(context, outcome);
2727
_telemetry.Report(new(ResilienceEventSeverity.Information, ChaosOutcomeConstants.OnOutcomeInjectedEvent), context, args);
@@ -31,12 +31,7 @@ protected internal override async ValueTask<Outcome<T>> ExecuteCore<TState>(Func
3131
await _onOutcomeInjected(args).ConfigureAwait(context.ContinueOnCapturedContext);
3232
}
3333

34-
if (outcome.HasResult)
35-
{
36-
return new Outcome<T>(outcome.Result);
37-
}
38-
39-
return new Outcome<T>(outcome.Exception!);
34+
return outcome;
4035
}
4136

4237
return await StrategyHelper.ExecuteCallbackSafeAsync(callback, context, state).ConfigureAwait(context.ContinueOnCapturedContext);

src/Polly.Core/Utils/DefaultPredicates.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,5 @@
33
internal static class DefaultPredicates<TArgs, TResult>
44
where TArgs : IOutcomeArguments<TResult>
55
{
6-
public static readonly Func<TArgs, ValueTask<bool>> HandleOutcome = args => args.Outcome.Exception switch
7-
{
8-
OperationCanceledException => PredicateResult.False(),
9-
Exception => PredicateResult.True(),
10-
_ => PredicateResult.False(),
11-
};
6+
public static readonly Func<TArgs, ValueTask<bool>> HandleOutcome = args => new(args.Outcome.Exception is { } and not OperationCanceledException);
127
}

src/Polly.Core/Utils/Guard.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ internal static class Guard
99
public static T NotNull<T>(T value, [CallerArgumentExpression("value")] string argumentName = "")
1010
where T : class
1111
{
12+
#if NET8_0_OR_GREATER
13+
ArgumentNullException.ThrowIfNull(value, argumentName);
14+
#else
1215
if (value is null)
1316
{
1417
throw new ArgumentNullException(argumentName);
1518
}
19+
#endif
1620

1721
return value;
1822
}

src/Polly.Core/Utils/Pipeline/ComponentDisposeHelper.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ public void EnsureNotDisposed()
2626
{
2727
if (_disposed)
2828
{
29-
throw new ObjectDisposedException("ResiliencePipeline", "This resilience pipeline has been disposed and cannot be used anymore.");
29+
ThrowDisposed();
3030
}
3131
}
3232

33+
private static void ThrowDisposed() => throw new ObjectDisposedException("ResiliencePipeline", "This resilience pipeline has been disposed and cannot be used anymore.");
34+
3335
public ValueTask ForceDisposeAsync()
3436
{
3537
_disposed = true;

src/Polly.Core/Utils/Pipeline/ExecutionTrackingComponent.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public ExecutionTrackingComponent(PipelineComponent component, TimeProvider time
1717

1818
public PipelineComponent Component { get; }
1919

20-
public bool HasPendingExecutions => Interlocked.CompareExchange(ref _pendingExecutions, 0, 0) > 0;
20+
public bool HasPendingExecutions => Volatile.Read(ref _pendingExecutions) > 0;
2121

2222
internal override async ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>(
2323
Func<ResilienceContext, TState, ValueTask<Outcome<TResult>>> callback,
@@ -39,7 +39,6 @@ internal override async ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState>
3939
public override async ValueTask DisposeAsync()
4040
{
4141
var start = _timeProvider.GetTimestamp();
42-
var stopwatch = Stopwatch.StartNew();
4342

4443
// We don't want to introduce locks or any synchronization primitives to main execution path
4544
// so we will do "dummy" retries until there are no more executions.

0 commit comments

Comments
 (0)