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 cfb42a9 commit 4244d2d
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 70 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
16 changes: 13 additions & 3 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 @@ -19,11 +21,19 @@ internal ResilienceStrategyTelemetry(ResilienceTelemetrySource source, Telemetry

internal TelemetryListener? Listener { get; }

internal ResilienceTelemetrySource TelemetrySource { get; }

/// <summary>
/// Gets BlahBlahBlah.
/// Updates the source of the telemetry on the provided exception.
/// </summary>
/// <value>dummy.</value>
public ResilienceTelemetrySource TelemetrySource { get; } // TODO: fix visibility
/// <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.
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
3 changes: 0 additions & 3 deletions src/Polly.RateLimiting/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
#nullable enable
Polly.RateLimiting.RateLimiterRejectedException.RateLimiterRejectedException(Polly.Telemetry.ResilienceTelemetrySource! telemetrySource) -> void
Polly.RateLimiting.RateLimiterRejectedException.RateLimiterRejectedException(Polly.Telemetry.ResilienceTelemetrySource! telemetrySource, System.TimeSpan retryAfter) -> void
Polly.RateLimiting.RateLimiterRejectedException.TelemetrySource.get -> string?
54 changes: 5 additions & 49 deletions src/Polly.RateLimiting/RateLimiterRejectedException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
#endif
using System.Threading.RateLimiting;

using Polly.Telemetry;

namespace Polly.RateLimiting;

/// <summary>
Expand All @@ -23,19 +21,6 @@ public RateLimiterRejectedException()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="RateLimiterRejectedException"/> class.
/// </summary>
/// <param name="telemetrySource">The source pipeline and strategy names.</param>
public RateLimiterRejectedException(ResilienceTelemetrySource telemetrySource)
: base("The operation could not be executed because it was rejected by the rate limiter.")
{
var pipelineName = telemetrySource?.PipelineName ?? "(null)";
var pipelineInstanceName = telemetrySource?.PipelineInstanceName ?? "(null)";
var strategyName = telemetrySource?.StrategyName ?? "(null)";
TelemetrySource = $"{pipelineName}/{pipelineInstanceName}/{strategyName}";
}

/// <summary>
/// Initializes a new instance of the <see cref="RateLimiterRejectedException"/> class.
/// </summary>
Expand All @@ -44,21 +29,6 @@ public RateLimiterRejectedException(TimeSpan retryAfter)
: base($"The operation could not be executed because it was rejected by the rate limiter. It can be retried after '{retryAfter}'.")
=> RetryAfter = retryAfter;

/// <summary>
/// Initializes a new instance of the <see cref="RateLimiterRejectedException"/> class.
/// </summary>
/// <param name="telemetrySource">The source pipeline and strategy names.</param>
/// <param name="retryAfter">The retry after value.</param>
public RateLimiterRejectedException(ResilienceTelemetrySource telemetrySource, TimeSpan retryAfter)
: base($"The operation could not be executed because it was rejected by the rate limiter. It can be retried after '{retryAfter}'.")
{
var pipelineName = telemetrySource?.PipelineName ?? "(null)";
var pipelineInstanceName = telemetrySource?.PipelineInstanceName ?? "(null)";
var strategyName = telemetrySource?.StrategyName ?? "(null)";
TelemetrySource = $"{pipelineName}/{pipelineInstanceName}/{strategyName}";
RetryAfter = retryAfter;
}

/// <summary>
/// Initializes a new instance of the <see cref="RateLimiterRejectedException"/> class.
/// </summary>
Expand All @@ -74,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 @@ -93,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 @@ -104,11 +76,6 @@ public RateLimiterRejectedException(string message, TimeSpan retryAfter, Excepti
/// </remarks>
public TimeSpan? RetryAfter { get; }

/// <summary>
/// Gets the name of the strategy which has thrown the exception.
/// </summary>
public string? TelemetrySource { get; }

#pragma warning disable RS0016 // Add public types and members to the declared API
#if !NETCOREAPP
/// <summary>
Expand All @@ -124,25 +91,14 @@ private RateLimiterRejectedException(SerializationInfo info, StreamingContext co
{
RetryAfter = TimeSpan.FromSeconds(retryAfter);
}

Source = info.GetString(nameof(Source));
}

/// <inheritdoc/>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
Guard.NotNull(info);

info.AddValue(nameof(Source), Source);

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

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

var source = _telemetry.TelemetrySource;
var exception = retryAfter.HasValue ? new RateLimiterRejectedException(source, retryAfter.Value) : new RateLimiterRejectedException(source);
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 4244d2d

Please sign in to comment.