diff --git a/Eon.Tests/Examples/PollyExamples.cs b/Eon.Tests/Examples/PollyExamples.cs index c8d3d92..cc40ab6 100644 --- a/Eon.Tests/Examples/PollyExamples.cs +++ b/Eon.Tests/Examples/PollyExamples.cs @@ -10,17 +10,15 @@ public static async Task Case1() { #region Example1 - // build any schedule using Eon + // build a schedule using Eon Schedule schedule = Schedule.Linear(1).Take(5); - // get the durations - var durations = schedule.ToArray(); // now create the options RetryStrategyOptions options = new RetryStrategyOptions { MaxRetryAttempts = schedule.Count ?? int.MaxValue, Delay = TimeSpan.Zero, DelayGenerator = x => - ValueTask.FromResult((TimeSpan)durations[x.AttemptNumber]) + ValueTask.FromResult((TimeSpan)schedule[x.AttemptNumber]) }; ResiliencePipeline pipeline = new ResiliencePipelineBuilder().AddRetry(options).Build(); int attempts = 0; @@ -43,7 +41,7 @@ public static async Task Case2() { #region Example2 - // build any schedule using Eon + // build a schedule using Eon Schedule schedule = Schedule.Linear(1); // now create the options RetryStrategyOptions options = new RetryStrategyOptions @@ -51,12 +49,7 @@ public static async Task Case2() MaxRetryAttempts = int.MaxValue, Delay = TimeSpan.Zero, DelayGenerator = x => - { - var enumerator = x.AttemptNumber == 0 ? schedule.GetEnumerator() : null; - return ValueTask.FromResult( - enumerator?.MoveNext() == true ? (TimeSpan)enumerator.Current : null - ); - } + ValueTask.FromResult((TimeSpan)schedule[x.AttemptNumber]) }; ResiliencePipeline pipeline = new ResiliencePipelineBuilder().AddRetry(options).Build(); int attempts = 0; diff --git a/Eon.Tests/ScheduleTests.cs b/Eon.Tests/ScheduleTests.cs index ad07586..7e4c811 100644 --- a/Eon.Tests/ScheduleTests.cs +++ b/Eon.Tests/ScheduleTests.cs @@ -17,4 +17,18 @@ public static void Case2() var schedule = Schedule.Forever; ((IEnumerable)schedule).GetEnumerator().MoveNext().Should().BeTrue(); } + + [Fact(DisplayName = "Schedule can be accessed by index")] + public static void Case3() + { + var schedule = Schedule.Linear(100); + schedule[0].Should().Be(100); + schedule[2].Should().Be(300); + schedule[3].Should().Be(400); + schedule[4].Should().Be(500); + schedule[5].Should().Be(600); + schedule[5].Should().Be(600); + schedule[100].Should().Be(10100); + schedule[1].Should().Be(200); + } } diff --git a/Eon/Schedule.cs b/Eon/Schedule.cs index 6aa4c73..8c0526a 100644 --- a/Eon/Schedule.cs +++ b/Eon/Schedule.cs @@ -34,4 +34,64 @@ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + + private readonly object _lock = new(); + private int _position; + private Duration[] _buffer = Array.Empty(); + private IEnumerator? _enumerator; + + /// + /// Provides `index` based access to a shared enumerated version of `this` . + /// This will grow the internal buffer in a synchronized way by doubling the current + /// length of the buffer when it is filled. + /// + /// This can only ever be as large as + /// This can be used to integrate into places that + /// take delegate based back-off algorithms + /// index + /// when the has + /// less emissions than the provided `index` + public Duration this[int index] + { + get + { + lock (_lock) + { + TryFillBuffer(index); + return _buffer[index]; + } + } + } + + private void TryFillBuffer(int index) + { + _enumerator ??= GetEnumerator(); + + var currentLength = _position; + if (currentLength != 0 && currentLength > index) + { + return; + } + + var size = currentLength == 0 ? 4 : currentLength * 2; + + if (size <= index) + { + size = index + 1; + } + + var newBuffer = new Duration[size]; + + if (_buffer.Length != 0) + { + Array.Copy(_buffer, 0, newBuffer, 0, _buffer.Length); + } + + for (; _position < size && _enumerator.MoveNext(); _position++) + { + newBuffer[_position] = _enumerator.Current; + } + + _buffer = newBuffer; + } }