Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/Polly/Bulkhead/BulkheadSemaphoreFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ public static (SemaphoreSlim MaxParallelizationSemaphore, SemaphoreSlim MaxQueue
{
var maxParallelizationSemaphore = new SemaphoreSlim(maxParallelization, maxParallelization);

var maxQueuingCompounded = maxQueueingActions <= int.MaxValue - maxParallelization
? maxQueueingActions + maxParallelization
: int.MaxValue;
var maxQueuingCompounded = Math.Min(maxQueueingActions + maxParallelization, int.MaxValue);
var maxQueuedActionsSemaphore = new SemaphoreSlim(maxQueuingCompounded, maxQueuingCompounded);

return (maxParallelizationSemaphore, maxQueuedActionsSemaphore);
Expand Down
129 changes: 80 additions & 49 deletions src/Polly/Caching/AsyncCacheTResultSyntax.cs

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions src/Polly/Caching/AsyncGenericCacheProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,16 @@ internal AsyncGenericCacheProvider(IAsyncCacheProvider nonGenericCacheProvider)

async Task<(bool, TCacheFormat?)> IAsyncCacheProvider<TCacheFormat>.TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext)
{
(bool cacheHit, object? result) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext);
return (cacheHit, (TCacheFormat?)(result ?? default(TCacheFormat)));
(bool cacheHit, object? cached) = await _wrappedCacheProvider.TryGetAsync(key, cancellationToken, continueOnCapturedContext).ConfigureAwait(continueOnCapturedContext);

TCacheFormat? result = default;

if (cacheHit)
{
result = (TCacheFormat?)cached;
}

return (cacheHit, result);
}

Task IAsyncCacheProvider<TCacheFormat>.PutAsync(string key, TCacheFormat? value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) =>
Expand Down
12 changes: 10 additions & 2 deletions src/Polly/Caching/GenericCacheProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,16 @@ internal GenericCacheProvider(ISyncCacheProvider nonGenericCacheProvider) =>

(bool, TCacheFormat?) ISyncCacheProvider<TCacheFormat>.TryGet(string key)
{
(bool cacheHit, object? result) = _wrappedCacheProvider.TryGet(key);
return (cacheHit, (TCacheFormat?)(result ?? default(TCacheFormat)));
(bool cacheHit, object? cached) = _wrappedCacheProvider.TryGet(key);

TCacheFormat? result = default;

if (cacheHit)
{
result = (TCacheFormat?)cached;
}

return (cacheHit, result);
}

void ISyncCacheProvider<TCacheFormat>.Put(string key, TCacheFormat? value, Ttl ttl) =>
Expand Down
5 changes: 2 additions & 3 deletions src/Polly/Caching/NonSlidingTtl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ protected NonSlidingTtl(DateTimeOffset absoluteExpirationTime) =>
/// <returns>A <see cref="Ttl"/> representing the remaining Ttl of the cached item.</returns>
public Ttl GetTtl(Context context, object? result)
{
TimeSpan untilPointInTime = absoluteExpirationTime.Subtract(SystemClock.DateTimeOffsetUtcNow());
TimeSpan remaining = untilPointInTime > TimeSpan.Zero ? untilPointInTime : TimeSpan.Zero;
return new Ttl(remaining, false);
long remaining = Math.Max(0, absoluteExpirationTime.Subtract(SystemClock.DateTimeOffsetUtcNow()).Ticks);
return new Ttl(TimeSpan.FromTicks(remaining), false);
}
}
9 changes: 6 additions & 3 deletions src/Polly/CircuitBreaker/AdvancedCircuitController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

internal sealed class AdvancedCircuitController<TResult> : CircuitStateController<TResult>
{
private const short NumberOfWindows = 10;
internal static readonly long ResolutionOfCircuitTimer = TimeSpan.FromMilliseconds(20).Ticks;

#pragma warning disable IDE0032 // Use auto property
private readonly IHealthMetrics _metrics;
#pragma warning restore IDE0032 // Use auto property
private readonly double _failureThreshold;
private readonly int _minimumThroughput;

Expand All @@ -19,14 +20,16 @@ public AdvancedCircuitController(
Action onHalfOpen)
: base(durationOfBreak, onBreak, onReset, onHalfOpen)
{
_metrics = samplingDuration.Ticks < ResolutionOfCircuitTimer * NumberOfWindows
_metrics = samplingDuration.Ticks < ResolutionOfCircuitTimer * RollingHealthMetrics.WindowCount
? new SingleHealthMetrics(samplingDuration)
: new RollingHealthMetrics(samplingDuration, NumberOfWindows);
: new RollingHealthMetrics(samplingDuration);

_failureThreshold = failureThreshold;
_minimumThroughput = minimumThroughput;
}

internal IHealthMetrics Metrics => _metrics; // For testing

public override void OnCircuitReset(Context context)
{
using var _ = TimedLock.Lock(Lock);
Expand Down
9 changes: 5 additions & 4 deletions src/Polly/CircuitBreaker/CircuitStateController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,11 @@ protected void Break_NeedsLock(Context context) =>

private void BreakFor_NeedsLock(TimeSpan durationOfBreak, Context context)
{
bool willDurationTakeUsPastDateTimeMaxValue = durationOfBreak > DateTime.MaxValue - SystemClock.UtcNow();
BlockedTill = willDurationTakeUsPastDateTimeMaxValue
? DateTime.MaxValue.Ticks
: (SystemClock.UtcNow() + durationOfBreak).Ticks;
// Prevent overflow if DurationOfBreak goes beyond the maximum possible DateTime
ulong utcNowTicks = (ulong)SystemClock.UtcNow().Ticks;
ulong durationOfBreakTicks = (ulong)durationOfBreak.Ticks;

BlockedTill = (long)Math.Min(utcNowTicks + durationOfBreakTicks, (ulong)DateTime.MaxValue.Ticks);

var transitionedState = InternalCircuitState;
InternalCircuitState = CircuitState.Open;
Expand Down
21 changes: 14 additions & 7 deletions src/Polly/CircuitBreaker/RollingHealthMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ namespace Polly.CircuitBreaker;

internal sealed class RollingHealthMetrics : IHealthMetrics
{
internal const short WindowCount = 10;

private readonly long _samplingDuration;
private readonly long _windowDuration;
private readonly Queue<HealthCount> _windows;

private HealthCount? _currentWindow;

public RollingHealthMetrics(TimeSpan samplingDuration, short numberOfWindows)
public RollingHealthMetrics(TimeSpan samplingDuration)
{
_samplingDuration = samplingDuration.Ticks;
_windowDuration = _samplingDuration / WindowCount;

_windowDuration = _samplingDuration / numberOfWindows;
_windows = new(numberOfWindows + 1);
// stryker disable once all : only affects capacity and not logic
_windows = new(WindowCount + 1);
}

public void IncrementSuccess_NeedsLock()
Expand Down Expand Up @@ -60,20 +63,24 @@ public HealthCount GetHealthCount_NeedsLock()
private HealthCount ActualiseCurrentMetric_NeedsLock()
{
var now = SystemClock.UtcNow().Ticks;
if (_currentWindow == null || now - _currentWindow.StartedAt >= _windowDuration)
var currentWindow = _currentWindow;

// stryker disable once all : no means to test this
if (currentWindow == null || now - currentWindow.StartedAt >= _windowDuration)
{
_currentWindow = new()
_currentWindow = currentWindow = new()
{
StartedAt = now
};
_windows.Enqueue(_currentWindow);
_windows.Enqueue(currentWindow);
}

// stryker disable once all : no means to test this
while (_windows.Count > 0 && now - _windows.Peek().StartedAt >= _samplingDuration)
{
_windows.Dequeue();
}

return _currentWindow;
return currentWindow;
}
}
2 changes: 1 addition & 1 deletion src/Polly/Policy.HandleSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,5 @@ public static PolicyBuilder<TResult> HandleResult(Func<TResult, bool> resultPred
/// <remarks>This policy filter matches the <paramref name="result"/> value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate.</remarks>
/// <returns>The PolicyBuilder instance.</returns>
public static PolicyBuilder<TResult> HandleResult(TResult result) =>
HandleResult(r => (!Equals(r, default(TResult)) && r.Equals(result)) || (Equals(r, default(TResult)) && Equals(result, default(TResult))));
HandleResult(r => EqualityComparer<TResult>.Default.Equals(r, result));
}
2 changes: 1 addition & 1 deletion src/Polly/Policy.SyncNonGenericImplementation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public abstract partial class Policy
/// <param name="cancellationToken">A token to signal that execution should be cancelled.</param>
[DebuggerStepThrough]
protected virtual void Implementation(Action<Context, CancellationToken> action, Context context, CancellationToken cancellationToken) =>
Implementation<EmptyStruct>((ctx, token) =>
Implementation((ctx, token) =>
{
action(ctx, token);
return EmptyStruct.Instance;
Expand Down
2 changes: 1 addition & 1 deletion src/Polly/PolicyBuilder.OrSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public PolicyBuilder<TResult> OrResult<TResult>(Func<TResult, bool> resultPredic
/// <remarks>This policy filter matches the <paramref name="result"/> value returned using .Equals(), ideally suited for value types such as int and enum. To match characteristics of class return types, consider the overload taking a result predicate.</remarks>
/// <returns>The PolicyBuilder instance.</returns>
public PolicyBuilder<TResult> OrResult<TResult>(TResult result) =>
OrResult<TResult>(r => (!Equals(r, default(TResult)) && r.Equals(result)) || (Equals(r, default(TResult)) && Equals(result, default(TResult))));
OrResult<TResult>(r => EqualityComparer<TResult>.Default.Equals(r, result));

#endregion
}
Expand Down
2 changes: 1 addition & 1 deletion src/Polly/Polly.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<AssemblyTitle>Polly</AssemblyTitle>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProjectType>Library</ProjectType>
<MutationScore>80</MutationScore>
<MutationScore>97</MutationScore>
<IncludePollyUsings>true</IncludePollyUsings>
<!-- We do not plan on enabling nullable annotations for Polly -->
<NoWarn>$(NoWarn);RS0037</NoWarn>
Expand Down
2 changes: 1 addition & 1 deletion src/Polly/RateLimit/AsyncRateLimitSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static AsyncRateLimitPolicy RateLimitAsync(
throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive.");
}

IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst);
IRateLimiter rateLimiter = new LockFreeTokenBucketRateLimiter(onePer, maxBurst);

return new AsyncRateLimitPolicy(rateLimiter);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Polly/RateLimit/AsyncRateLimitTResultSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public static AsyncRateLimitPolicy<TResult> RateLimitAsync<TResult>(
throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive.");
}

IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst);
IRateLimiter rateLimiter = new LockFreeTokenBucketRateLimiter(onePer, maxBurst);

return new AsyncRateLimitPolicy<TResult>(rateLimiter, retryAfterFactory);
}
Expand Down
5 changes: 0 additions & 5 deletions src/Polly/RateLimit/LockFreeTokenBucketRateLimiter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ internal sealed class LockFreeTokenBucketRateLimiter : IRateLimiter
/// </param>
public LockFreeTokenBucketRateLimiter(TimeSpan onePer, long bucketCapacity)
{
if (onePer <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(onePer), onePer, $"The {nameof(LockFreeTokenBucketRateLimiter)} must specify a positive TimeSpan for how often an execution is permitted.");
}

_addTokenTickInterval = onePer.Ticks;
_bucketCapacity = bucketCapacity;

Expand Down
2 changes: 1 addition & 1 deletion src/Polly/RateLimit/RateLimitSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static RateLimitPolicy RateLimit(
throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive.");
}

IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst);
IRateLimiter rateLimiter = new LockFreeTokenBucketRateLimiter(onePer, maxBurst);

return new RateLimitPolicy(rateLimiter);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Polly/RateLimit/RateLimitTResultSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public static RateLimitPolicy<TResult> RateLimit<TResult>(
throw new ArgumentOutOfRangeException(nameof(perTimeSpan), perTimeSpan, "The number of executions per timespan must be positive.");
}

IRateLimiter rateLimiter = RateLimiterFactory.Create(onePer, maxBurst);
IRateLimiter rateLimiter = new LockFreeTokenBucketRateLimiter(onePer, maxBurst);

return new RateLimitPolicy<TResult>(rateLimiter, retryAfterFactory);
}
Expand Down
8 changes: 0 additions & 8 deletions src/Polly/RateLimit/RateLimiterFactory.cs

This file was deleted.

16 changes: 14 additions & 2 deletions src/Polly/Registry/PolicyRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,14 @@ public TPolicy Get<TPolicy>(string key)
public bool TryGet<TPolicy>(string key, out TPolicy policy)
where TPolicy : IsPolicy
{
policy = default;
bool got = _registry.TryGetValue(key, out IsPolicy value);
policy = got ? (TPolicy)value : default;

if (got)
{
policy = (TPolicy)value;
}

return got;
}

Expand Down Expand Up @@ -158,8 +164,14 @@ public bool TryRemove<TPolicy>(string key, out TPolicy policy)
{
var registry = ThrowIfNotConcurrentImplementation();

policy = default;
bool got = registry.TryRemove(key, out IsPolicy value);
policy = got ? (TPolicy)value : default;

if (got)
{
policy = (TPolicy)value;
}

return got;
}

Expand Down
Loading