Skip to content

Commit 5f02610

Browse files
authored
Fix wrong randomFactor argument type on RetrySupport.Retry() (#8061)
* Fix wrong randomFactor argument type on RetrySupport.Retry() * Update API Approval list * Add unit tests
1 parent d8e2b1f commit 5f02610

4 files changed

Lines changed: 132 additions & 8 deletions

File tree

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.DotNet.verified.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4524,7 +4524,7 @@ namespace Akka.Pattern
45244524
public static System.Threading.Tasks.Task<T> Retry<T>(System.Func<System.Threading.Tasks.Task<T>> attempt, int attempts) { }
45254525
public static System.Threading.Tasks.Task<T> Retry<T>(System.Func<System.Threading.Tasks.Task<T>> attempt, int attempts, System.Func<int, Akka.Util.Option<System.TimeSpan>> delayFunction, Akka.Actor.IScheduler scheduler) { }
45264526
public static System.Threading.Tasks.Task<T> Retry<T>(System.Func<System.Threading.Tasks.Task<T>> attempt, int attempts, System.TimeSpan delay, Akka.Actor.IScheduler scheduler) { }
4527-
public static System.Threading.Tasks.Task<T> Retry<T>(System.Func<System.Threading.Tasks.Task<T>> attempt, int attempts, System.TimeSpan minBackoff, System.TimeSpan maxBackoff, int randomFactor, Akka.Actor.IScheduler scheduler) { }
4527+
public static System.Threading.Tasks.Task<T> Retry<T>(System.Func<System.Threading.Tasks.Task<T>> attempt, int attempts, System.TimeSpan minBackoff, System.TimeSpan maxBackoff, double randomFactor, Akka.Actor.IScheduler scheduler) { }
45284528
}
45294529
public class UserCalledFailException : Akka.Actor.AkkaException
45304530
{

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveCore.Net.verified.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4541,7 +4541,7 @@ namespace Akka.Pattern
45414541
public static System.Threading.Tasks.Task<T> Retry<T>(System.Func<System.Threading.Tasks.Task<T>> attempt, int attempts) { }
45424542
public static System.Threading.Tasks.Task<T> Retry<T>(System.Func<System.Threading.Tasks.Task<T>> attempt, int attempts, System.Func<int, Akka.Util.Option<System.TimeSpan>> delayFunction, Akka.Actor.IScheduler scheduler) { }
45434543
public static System.Threading.Tasks.Task<T> Retry<T>(System.Func<System.Threading.Tasks.Task<T>> attempt, int attempts, System.TimeSpan delay, Akka.Actor.IScheduler scheduler) { }
4544-
public static System.Threading.Tasks.Task<T> Retry<T>(System.Func<System.Threading.Tasks.Task<T>> attempt, int attempts, System.TimeSpan minBackoff, System.TimeSpan maxBackoff, int randomFactor, Akka.Actor.IScheduler scheduler) { }
4544+
public static System.Threading.Tasks.Task<T> Retry<T>(System.Func<System.Threading.Tasks.Task<T>> attempt, int attempts, System.TimeSpan minBackoff, System.TimeSpan maxBackoff, double randomFactor, Akka.Actor.IScheduler scheduler) { }
45454545
}
45464546
public class UserCalledFailException : Akka.Actor.AkkaException
45474547
{
@@ -5680,4 +5680,4 @@ namespace Akka.Util.Reflection
56805680
{
56815681
public static System.Type GetType(string typeName) { }
56825682
}
5683-
}
5683+
}

src/core/Akka.Tests/Pattern/RetrySpec.cs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,5 +188,129 @@ await WithinAsync(TimeSpan.FromSeconds(3), async () =>
188188
Assert.Equal(5, remaining);
189189
});
190190
}
191+
192+
[Fact]
193+
public async Task Pattern_Retry_with_backoff_must_run_a_successful_task_immediately()
194+
{
195+
await WithinAsync(TimeSpan.FromSeconds(3), async () =>
196+
{
197+
var remaining = await Retry(
198+
() => Task.FromResult(5), 5,
199+
TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1), 0.2, Sys.Scheduler);
200+
Assert.Equal(5, remaining);
201+
});
202+
}
203+
204+
[Fact]
205+
public async Task Pattern_Retry_with_backoff_must_return_a_success_for_a_task_that_succeeds_eventually()
206+
{
207+
var failCount = 0;
208+
209+
Task<int> Attempt()
210+
{
211+
if (failCount < 3)
212+
{
213+
failCount += 1;
214+
return Task.FromException<int>(new InvalidOperationException(failCount.ToString()));
215+
}
216+
return Task.FromResult(5);
217+
}
218+
219+
await WithinAsync(TimeSpan.FromSeconds(10), async () =>
220+
{
221+
var remaining = await Retry(
222+
Attempt, 5,
223+
TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(500), 0.0, Sys.Scheduler);
224+
Assert.Equal(5, remaining);
225+
});
226+
}
227+
228+
[Fact]
229+
public async Task Pattern_Retry_with_backoff_must_return_a_failure_when_retries_exhausted()
230+
{
231+
var failCount = 0;
232+
233+
Task<int> Attempt()
234+
{
235+
failCount += 1;
236+
return Task.FromException<int>(new InvalidOperationException(failCount.ToString()));
237+
}
238+
239+
await WithinAsync(TimeSpan.FromSeconds(10), async () =>
240+
{
241+
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
242+
await Retry(
243+
Attempt, 3,
244+
TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(200), 0.0, Sys.Scheduler));
245+
Assert.Equal("4", exception.Message);
246+
});
247+
}
248+
249+
[Fact]
250+
public async Task Pattern_Retry_with_backoff_must_accept_double_randomFactor()
251+
{
252+
var failCount = 0;
253+
254+
Task<int> Attempt()
255+
{
256+
if (failCount < 2)
257+
{
258+
failCount += 1;
259+
return Task.FromException<int>(new InvalidOperationException(failCount.ToString()));
260+
}
261+
return Task.FromResult(42);
262+
}
263+
264+
await WithinAsync(TimeSpan.FromSeconds(10), async () =>
265+
{
266+
// This verifies the fix: randomFactor is now double, allowing fractional values like 0.2
267+
var remaining = await Retry(
268+
Attempt, 5,
269+
TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(500), 0.2, Sys.Scheduler);
270+
Assert.Equal(42, remaining);
271+
});
272+
}
273+
274+
[Fact]
275+
public void Pattern_Retry_with_backoff_must_throw_on_null_attempt()
276+
{
277+
Assert.Throws<ArgumentNullException>(() =>
278+
{
279+
_ = Retry<int>(null, 5,
280+
TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1), 0.2, Sys.Scheduler);
281+
});
282+
}
283+
284+
[Fact]
285+
public void Pattern_Retry_with_backoff_must_throw_on_invalid_minBackoff()
286+
{
287+
Assert.Throws<ArgumentException>(() =>
288+
{
289+
_ = Retry(() => Task.FromResult(1), 5,
290+
TimeSpan.Zero, TimeSpan.FromSeconds(1), 0.2, Sys.Scheduler);
291+
});
292+
}
293+
294+
[Fact]
295+
public void Pattern_Retry_with_backoff_must_throw_when_maxBackoff_less_than_minBackoff()
296+
{
297+
Assert.Throws<ArgumentException>(() =>
298+
{
299+
_ = Retry(() => Task.FromResult(1), 5,
300+
TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(1), 0.2, Sys.Scheduler);
301+
});
302+
}
303+
304+
[Theory]
305+
[InlineData(-0.1)]
306+
[InlineData(1.1)]
307+
public void Pattern_Retry_with_backoff_must_throw_on_invalid_randomFactor(double randomFactor)
308+
{
309+
Assert.Throws<ArgumentException>(() =>
310+
{
311+
_ = Retry(() => Task.FromResult(1), 5,
312+
TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1), randomFactor, Sys.Scheduler);
313+
});
314+
}
191315
}
192316
}

src/core/Akka/Pattern/RetrySupport.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ public static Task<T> Retry<T>(Func<Task<T>> attempt, int attempts) =>
4545
/// <param name="maxBackoff">the exponential back-off is capped to this duration.</param>
4646
/// <param name="randomFactor">after calculation of the exponential back-off an additional random delay based on this factor is added, e.g. `0.2` adds up to `20%` delay. In order to skip this additional delay pass in `0`.</param>
4747
/// <param name="scheduler">The scheduler instance to use.</param>
48-
public static Task<T> Retry<T>(Func<Task<T>> attempt, int attempts, TimeSpan minBackoff, TimeSpan maxBackoff, int randomFactor, IScheduler scheduler)
48+
public static Task<T> Retry<T>(Func<Task<T>> attempt, int attempts, TimeSpan minBackoff, TimeSpan maxBackoff, double randomFactor, IScheduler scheduler)
4949
{
50-
if (attempt == null) throw new ArgumentNullException("Parameter attempt should not be null.");
51-
if (minBackoff <= TimeSpan.Zero) throw new ArgumentException("Parameter minBackoff must be > 0");
52-
if (maxBackoff < minBackoff) throw new ArgumentException("Parameter maxBackoff must be >= minBackoff");
53-
if (randomFactor < 0.0 || randomFactor > 1.0) throw new ArgumentException("RandomFactor must be between 0.0 and 1.0");
50+
if (attempt == null) throw new ArgumentNullException(nameof(attempt), "Parameter attempt should not be null.");
51+
if (minBackoff <= TimeSpan.Zero) throw new ArgumentException("Parameter minBackoff must be > 0", nameof(minBackoff));
52+
if (maxBackoff < minBackoff) throw new ArgumentException("Parameter maxBackoff must be >= minBackoff", nameof(maxBackoff));
53+
if (randomFactor is < 0.0 or > 1.0) throw new ArgumentException("RandomFactor must be between 0.0 and 1.0", nameof(randomFactor));
5454

5555
return Retry(attempt, attempts, attempted => BackoffSupervisor.CalculateDelay(attempted, minBackoff, maxBackoff, randomFactor), scheduler);
5656
}

0 commit comments

Comments
 (0)