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 dc687af commit 1d98646
Show file tree
Hide file tree
Showing 10 changed files with 33 additions and 139 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; }

}
3 changes: 2 additions & 1 deletion src/Polly.Core/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +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?
Polly.Telemetry.ResilienceStrategyTelemetry.AsTelemetrySourceString() -> string!
virtual Polly.ExecutionRejectedException.TelemetrySource.get -> Polly.Telemetry.ResilienceTelemetrySource?
Polly.Telemetry.ResilienceStrategyTelemetry.UpdateTelemetrySource(Polly.ExecutionRejectedException! exception) -> void
16 changes: 9 additions & 7 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 @@ -22,15 +24,15 @@ internal ResilienceStrategyTelemetry(ResilienceTelemetrySource source, Telemetry
internal ResilienceTelemetrySource TelemetrySource { get; }

/// <summary>
/// Returns a string representation of the source of the telemetry.
/// Updates the source of the telemetry on the provided exception.
/// </summary>
/// <returns>The string representation of the source of the telemetry.</returns>
public string AsTelemetrySourceString()
/// <param name="exception">The to-be-updated exception.</param>
[EditorBrowsable(EditorBrowsableState.Never)]
public void UpdateTelemetrySource(ExecutionRejectedException exception)
{
var pipelineName = TelemetrySource?.PipelineName ?? "(null)";
var pipelineInstanceName = TelemetrySource?.PipelineInstanceName ?? "(null)";
var strategyName = TelemetrySource?.StrategyName ?? "(null)";
return $"{pipelineName}/{pipelineInstanceName}/{strategyName}";
Guard.NotNull(exception);

exception.TelemetrySource = TelemetrySource;
}

/// <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
5 changes: 0 additions & 5 deletions src/Polly.RateLimiting/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
#nullable enable
Polly.RateLimiting.RateLimiterRejectedException.RateLimiterRejectedException(string! message, string! telemetrySource) -> void
Polly.RateLimiting.RateLimiterRejectedException.RateLimiterRejectedException(string! message, string! telemetrySource, System.Exception! inner) -> void
Polly.RateLimiting.RateLimiterRejectedException.RateLimiterRejectedException(string! message, string! telemetrySource, System.TimeSpan retryAfter) -> void
Polly.RateLimiting.RateLimiterRejectedException.RateLimiterRejectedException(string! message, string! telemetrySource, System.TimeSpan retryAfter, System.Exception! inner) -> void
Polly.RateLimiting.RateLimiterRejectedException.TelemetrySource.get -> string?
52 changes: 0 additions & 52 deletions src/Polly.RateLimiting/RateLimiterRejectedException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,6 @@ public RateLimiterRejectedException(string message)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="RateLimiterRejectedException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="telemetrySource">A string representing the source of the telemetry.</param>
public RateLimiterRejectedException(string message, string telemetrySource)
: base(message)
=> TelemetrySource = telemetrySource;

/// <summary>
/// Initializes a new instance of the <see cref="RateLimiterRejectedException"/> class.
/// </summary>
Expand All @@ -56,16 +47,6 @@ public RateLimiterRejectedException(string message, TimeSpan retryAfter)
: base(message)
=> RetryAfter = retryAfter;

/// <summary>
/// Initializes a new instance of the <see cref="RateLimiterRejectedException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="telemetrySource">A string representing the source of the telemetry.</param>
/// <param name="retryAfter">The retry after value.</param>
public RateLimiterRejectedException(string message, string telemetrySource, TimeSpan retryAfter)
: base(message)
=> (TelemetrySource, RetryAfter) = (telemetrySource, retryAfter);

/// <summary>
/// Initializes a new instance of the <see cref="RateLimiterRejectedException"/> class.
/// </summary>
Expand All @@ -76,16 +57,6 @@ public RateLimiterRejectedException(string message, Exception inner)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="RateLimiterRejectedException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="telemetrySource">A string representing the source of the telemetry.</param>
/// <param name="inner">The inner exception.</param>
public RateLimiterRejectedException(string message, string telemetrySource, Exception inner)
: base(message, inner)
=> TelemetrySource = telemetrySource;

/// <summary>
/// Initializes a new instance of the <see cref="RateLimiterRejectedException"/> class.
/// </summary>
Expand All @@ -96,17 +67,6 @@ public RateLimiterRejectedException(string message, TimeSpan retryAfter, Excepti
: base(message, inner)
=> RetryAfter = retryAfter;

/// <summary>
/// Initializes a new instance of the <see cref="RateLimiterRejectedException"/> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="telemetrySource">A string representing the source of the telemetry.</param>
/// <param name="retryAfter">The retry after value.</param>
/// <param name="inner">The inner exception.</param>
public RateLimiterRejectedException(string message, string telemetrySource, TimeSpan retryAfter, Exception inner)
: base(message, inner)
=> (TelemetrySource, RetryAfter) = (telemetrySource, retryAfter);

/// <summary>
/// Gets the amount of time to wait before retrying again.
/// </summary>
Expand All @@ -116,11 +76,6 @@ public RateLimiterRejectedException(string message, string telemetrySource, Time
/// </remarks>
public TimeSpan? RetryAfter { get; }

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

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

var telemetrySource = info.GetString(nameof(TelemetrySource));
if (telemetrySource is not null)
{
TelemetrySource = telemetrySource;
}
}

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

info.AddValue(nameof(TelemetrySource), TelemetrySource ?? "(null)/(null)/(null)");
info.AddValue(nameof(RetryAfter), RetryAfter.HasValue ? RetryAfter.Value.TotalSeconds : -1.0);

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

const string Message = "The operation could not be executed because it was rejected by the rate limiter.";
var source = _telemetry.AsTelemetrySourceString();

var exception = retryAfter.HasValue
? new RateLimiterRejectedException($"{Message} It can be retried after '{retryAfter.Value}'.", source, retryAfter.Value)
: new RateLimiterRejectedException(Message, source);
? new RateLimiterRejectedException(retryAfter.Value)
: new RateLimiterRejectedException();

_telemetry.UpdateTelemetrySource(exception);

return Outcome.FromException<TResult>(exception.TrySetStackTrace());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,6 @@ public void Enabled_Ok()
new ResilienceStrategyTelemetry(_source, null).Enabled.Should().BeFalse();
}

[Fact]
public void AsTelemetrySourceString_Ok()
{
var source = new ResilienceTelemetrySource("builder", "instance", "strategy_name");
new ResilienceStrategyTelemetry(source, null).AsTelemetrySourceString().Should().Be("builder/instance/strategy_name");
}

[Fact]
public void AsTelemetrySourceString_Null_Ok()
{
ResilienceTelemetrySource? source = null;
new ResilienceStrategyTelemetry(source!, null).AsTelemetrySourceString().Should().Be("(null)/(null)/(null)");
}

[Fact]
public void AsTelemetrySourceString_Nulls_Ok()
{
var source = new ResilienceTelemetrySource(null, null, null);
new ResilienceStrategyTelemetry(source, null).AsTelemetrySourceString().Should().Be("(null)/(null)/(null)");
}

[Fact]
public void Report_NoOutcome_OK()
{
Expand Down
44 changes: 0 additions & 44 deletions test/Polly.RateLimiting.Tests/RateLimiterRejectedExceptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ namespace Polly.Core.Tests.Timeout;

public class RateLimiterRejectedExceptionTests
{
private readonly string _telemetrySource = "MyPipeline/MyPipelineInstance/MyRateLimiterStrategy";
private readonly string _message = "dummy";
private readonly TimeSpan _retryAfter = TimeSpan.FromSeconds(4);

Expand Down Expand Up @@ -38,16 +37,6 @@ public void Ctor_Message_Ok()
exception.TelemetrySource.Should().BeNull();
}

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

[Fact]
public void Ctor_Message_RetryAfter_Ok()
{
Expand All @@ -58,16 +47,6 @@ public void Ctor_Message_RetryAfter_Ok()
exception.TelemetrySource.Should().BeNull();
}

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

[Fact]
public void Ctor_Message_InnerException_Ok()
{
Expand All @@ -78,16 +57,6 @@ public void Ctor_Message_InnerException_Ok()
exception.TelemetrySource.Should().BeNull();
}

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

[Fact]
public void Ctor_Message_RetryAfter_InnerException_Ok()
{
Expand All @@ -98,16 +67,6 @@ public void Ctor_Message_RetryAfter_InnerException_Ok()
exception.TelemetrySource.Should().BeNull();
}

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

#if !NETCOREAPP
[Fact]
public void BinaryDeserialization_Ok()
Expand All @@ -118,9 +77,6 @@ public void BinaryDeserialization_Ok()

result = SerializeAndDeserializeException(new RateLimiterRejectedException());
result.RetryAfter.Should().BeNull();

result = SerializeAndDeserializeException(new RateLimiterRejectedException("message", _telemetrySource));
result.TelemetrySource.Should().Be(_telemetrySource);
}

public static T SerializeAndDeserializeException<T>(T exception)
Expand Down

0 comments on commit 1d98646

Please sign in to comment.