Skip to content

Commit

Permalink
Add TelemetrySource to ExecutionRejectedException
Browse files Browse the repository at this point in the history
  • Loading branch information
peter-csala committed Oct 21, 2024
1 parent 14ac109 commit ef63b0b
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ public ValueTask IsolateCircuitAsync(ResilienceContext context)

lock (_lock)
{
SetLastHandledOutcome_NeedsLock(Outcome.FromException<T>(new IsolatedCircuitException()));
var exception = new IsolatedCircuitException();
_telemetry.UpdateTelemetrySource(exception);
SetLastHandledOutcome_NeedsLock(Outcome.FromException<T>(exception));
OpenCircuitFor_NeedsLock(Outcome.FromResult<T>(default), TimeSpan.MaxValue, manual: true, context, out task);
_circuitState = CircuitState.Isolated;
}
Expand Down Expand Up @@ -123,7 +125,7 @@ public ValueTask CloseCircuitAsync(ResilienceContext context)
{
EnsureNotDisposed();

Exception? exception = null;
BrokenCircuitException? exception = null;
bool isHalfOpen = false;

Task? task = null;
Expand Down Expand Up @@ -157,6 +159,7 @@ public ValueTask CloseCircuitAsync(ResilienceContext context)

if (exception is not null)
{
_telemetry.UpdateTelemetrySource(exception);
return Outcome.FromException<T>(exception);
}

Expand Down Expand Up @@ -308,11 +311,13 @@ private void SetLastHandledOutcome_NeedsLock(Outcome<T> outcome)
private BrokenCircuitException CreateBrokenCircuitException()
{
TimeSpan retryAfter = _blockedUntil - _timeProvider.GetUtcNow();
return _breakingException switch
BrokenCircuitException exception = _breakingException switch
{
Exception exception => new BrokenCircuitException(BrokenCircuitException.DefaultMessage, retryAfter, exception),
Exception ex => new BrokenCircuitException(BrokenCircuitException.DefaultMessage, retryAfter, ex),
_ => new BrokenCircuitException(BrokenCircuitException.DefaultMessage, retryAfter)
};
_telemetry.UpdateTelemetrySource(exception);
return exception;
}

private void OpenCircuit_NeedsLock(Outcome<T> outcome, bool manual, ResilienceContext context, out Task? scheduledTask)
Expand Down
8 changes: 8 additions & 0 deletions src/Polly.Core/ExecutionRejectedException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Runtime.Serialization;
#endif

using Polly.Telemetry;

namespace Polly;

/// <summary>
Expand Down Expand Up @@ -49,4 +51,10 @@ protected ExecutionRejectedException(SerializationInfo info, StreamingContext co
}
#endif
#pragma warning restore RS0016 // Add public types and members to the declared API

/// <summary>
/// Gets the source of the strategy which has thrown the exception, if known.
/// </summary>
public virtual ResilienceTelemetrySource? TelemetrySource { get; internal set; }

}
2 changes: 2 additions & 0 deletions src/Polly.Core/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ Polly.CircuitBreaker.BrokenCircuitException.BrokenCircuitException(string! messa
Polly.CircuitBreaker.BrokenCircuitException.BrokenCircuitException(string! message, System.TimeSpan retryAfter, System.Exception! inner) -> void
Polly.CircuitBreaker.BrokenCircuitException.BrokenCircuitException(System.TimeSpan retryAfter) -> void
Polly.CircuitBreaker.BrokenCircuitException.RetryAfter.get -> System.TimeSpan?
virtual Polly.ExecutionRejectedException.TelemetrySource.get -> Polly.Telemetry.ResilienceTelemetrySource?
Polly.Telemetry.ResilienceStrategyTelemetry.UpdateTelemetrySource(Polly.ExecutionRejectedException! exception) -> void
14 changes: 14 additions & 0 deletions src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.ComponentModel;

namespace Polly.Telemetry;

/// <summary>
Expand All @@ -21,6 +23,18 @@ internal ResilienceStrategyTelemetry(ResilienceTelemetrySource source, Telemetry

internal ResilienceTelemetrySource TelemetrySource { get; }

/// <summary>
/// Updates the source of the telemetry on the provided exception.
/// </summary>
/// <param name="exception">The to-be-updated exception.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public void UpdateTelemetrySource(ExecutionRejectedException exception)
{
Guard.NotNull(exception);

exception.TelemetrySource = TelemetrySource;
}

/// <summary>
/// Reports an event that occurred in a resilience strategy.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/Polly.Core/Timeout/TimeoutResilienceStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ protected internal override async ValueTask<Outcome<TResult>> ExecuteCore<TResul
timeout,
e);

_telemetry.UpdateTelemetrySource(timeoutException);
return Outcome.FromException<TResult>(timeoutException.TrySetStackTrace());
}

Expand Down
21 changes: 8 additions & 13 deletions src/Polly.RateLimiting/RateLimiterRejectedException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ public RateLimiterRejectedException(string message)
/// <param name="message">The message that describes the error.</param>
/// <param name="retryAfter">The retry after value.</param>
public RateLimiterRejectedException(string message, TimeSpan retryAfter)
: base(message) => RetryAfter = retryAfter;
: base(message)
=> RetryAfter = retryAfter;

/// <summary>
/// Initializes a new instance of the <see cref="RateLimiterRejectedException"/> class.
Expand All @@ -63,7 +64,8 @@ public RateLimiterRejectedException(string message, Exception inner)
/// <param name="retryAfter">The retry after value.</param>
/// <param name="inner">The inner exception.</param>
public RateLimiterRejectedException(string message, TimeSpan retryAfter, Exception inner)
: base(message, inner) => RetryAfter = retryAfter;
: base(message, inner)
=> RetryAfter = retryAfter;

/// <summary>
/// Gets the amount of time to wait before retrying again.
Expand All @@ -84,10 +86,10 @@ public RateLimiterRejectedException(string message, TimeSpan retryAfter, Excepti
private RateLimiterRejectedException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
var value = info.GetDouble("RetryAfter");
if (value >= 0.0)
var retryAfter = info.GetDouble(nameof(RetryAfter));
if (retryAfter >= 0.0)
{
RetryAfter = TimeSpan.FromSeconds(value);
RetryAfter = TimeSpan.FromSeconds(retryAfter);
}
}

Expand All @@ -96,14 +98,7 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont
{
Guard.NotNull(info);

if (RetryAfter.HasValue)
{
info.AddValue("RetryAfter", RetryAfter.Value.TotalSeconds);
}
else
{
info.AddValue("RetryAfter", -1.0);
}
info.AddValue(nameof(RetryAfter), RetryAfter.HasValue ? RetryAfter.Value.TotalSeconds : -1.0);

base.GetObjectData(info, context);
}
Expand Down
6 changes: 5 additions & 1 deletion src/Polly.RateLimiting/RateLimiterResilienceStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ protected override async ValueTask<Outcome<TResult>> ExecuteCore<TResult, TState
await OnLeaseRejected(new OnRateLimiterRejectedArguments(context, lease)).ConfigureAwait(context.ContinueOnCapturedContext);
}

var exception = retryAfter.HasValue ? new RateLimiterRejectedException(retryAfter.Value) : new RateLimiterRejectedException();
var exception = retryAfter.HasValue
? new RateLimiterRejectedException(retryAfter.Value)
: new RateLimiterRejectedException();

_telemetry.UpdateTelemetrySource(exception);

return Outcome.FromException<TResult>(exception.TrySetStackTrace());
}
Expand Down
66 changes: 57 additions & 9 deletions test/Polly.RateLimiting.Tests/RateLimiterRejectedExceptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,67 @@ namespace Polly.Core.Tests.Timeout;

public class RateLimiterRejectedExceptionTests
{
private readonly string _message = "dummy";
private readonly TimeSpan _retryAfter = TimeSpan.FromSeconds(4);

[Fact]
public void Ctor_Ok()
{
var retryAfter = TimeSpan.FromSeconds(4);
var exception = new RateLimiterRejectedException();
exception.InnerException.Should().BeNull();
exception.Message.Should().Be("The operation could not be executed because it was rejected by the rate limiter.");
exception.RetryAfter.Should().BeNull();
exception.TelemetrySource.Should().BeNull();
}

[Fact]
public void Ctor_RetryAfter_Ok()
{
var exception = new RateLimiterRejectedException(_retryAfter);
exception.InnerException.Should().BeNull();
exception.Message.Should().Be($"The operation could not be executed because it was rejected by the rate limiter. It can be retried after '00:00:04'.");
exception.RetryAfter.Should().Be(_retryAfter);
exception.TelemetrySource.Should().BeNull();
}

[Fact]
public void Ctor_Message_Ok()
{
var exception = new RateLimiterRejectedException(_message);
exception.InnerException.Should().BeNull();
exception.Message.Should().Be(_message);
exception.RetryAfter.Should().BeNull();
exception.TelemetrySource.Should().BeNull();
}

[Fact]
public void Ctor_Message_RetryAfter_Ok()
{
var exception = new RateLimiterRejectedException(_message, _retryAfter);
exception.InnerException.Should().BeNull();
exception.Message.Should().Be(_message);
exception.RetryAfter.Should().Be(_retryAfter);
exception.TelemetrySource.Should().BeNull();
}

[Fact]
public void Ctor_Message_InnerException_Ok()
{
var exception = new RateLimiterRejectedException(_message, new InvalidOperationException());
exception.InnerException.Should().BeOfType<InvalidOperationException>();
exception.Message.Should().Be(_message);
exception.RetryAfter.Should().BeNull();
exception.TelemetrySource.Should().BeNull();
}

new RateLimiterRejectedException().Message.Should().Be("The operation could not be executed because it was rejected by the rate limiter.");
new RateLimiterRejectedException().RetryAfter.Should().BeNull();
new RateLimiterRejectedException("dummy").Message.Should().Be("dummy");
new RateLimiterRejectedException("dummy", new InvalidOperationException()).Message.Should().Be("dummy");
new RateLimiterRejectedException(retryAfter).RetryAfter.Should().Be(retryAfter);
new RateLimiterRejectedException(retryAfter).Message.Should().Be($"The operation could not be executed because it was rejected by the rate limiter. It can be retried after '{retryAfter}'.");
new RateLimiterRejectedException("dummy", retryAfter).RetryAfter.Should().Be(retryAfter);
new RateLimiterRejectedException("dummy", retryAfter, new InvalidOperationException()).RetryAfter.Should().Be(retryAfter);
[Fact]
public void Ctor_Message_RetryAfter_InnerException_Ok()
{
var exception = new RateLimiterRejectedException(_message, _retryAfter, new InvalidOperationException());
exception.InnerException.Should().BeOfType<InvalidOperationException>();
exception.Message.Should().Be(_message);
exception.RetryAfter.Should().Be(_retryAfter);
exception.TelemetrySource.Should().BeNull();
}

#if !NETCOREAPP
Expand Down

0 comments on commit ef63b0b

Please sign in to comment.