diff --git a/src/java/org/apache/cassandra/config/RetrySpec.java b/src/java/org/apache/cassandra/config/RetrySpec.java index ff9b58f827ae..5ad1bead8792 100644 --- a/src/java/org/apache/cassandra/config/RetrySpec.java +++ b/src/java/org/apache/cassandra/config/RetrySpec.java @@ -22,12 +22,15 @@ import javax.annotation.Nullable; +import accord.utils.RandomSource; import org.apache.cassandra.config.DurationSpec.LongMillisecondsBound; import org.apache.cassandra.repair.SharedContext; import org.apache.cassandra.service.RetryStrategy; import org.apache.cassandra.service.TimeoutStrategy.LatencySourceFactory; import org.apache.cassandra.service.WaitStrategy; +import static org.apache.cassandra.service.RetryStrategy.randomizers; + public class RetrySpec { public static class MaxAttempt @@ -161,7 +164,9 @@ public static WaitStrategy toStrategy(SharedContext ctx, RetrySpec spec) { if (!spec.isEnabled()) return WaitStrategy.None.INSTANCE; - return RetryStrategy.parse(spec.baseSleepTime.toMilliseconds() + "ms * 2^attempts <= " + spec.maxSleepTime.toMilliseconds() + "ms,retries=" + (spec.maxAttempts.value - 1), LatencySourceFactory.none()); + RandomSource randomSource = RandomSource.wrap(ctx.random().get()); + RetryStrategy.WaitRandomizer randomizer = randomizers(randomSource).uniform(); + return RetryStrategy.parse("0ms ... " + spec.baseSleepTime.toMilliseconds() + "ms * attempts <= " + spec.maxSleepTime.toMilliseconds() + "ms,retries=" + (spec.maxAttempts.value - 1), LatencySourceFactory.none(), randomizer); } @Override diff --git a/test/unit/org/apache/cassandra/service/RetryStrategyTest.java b/test/unit/org/apache/cassandra/service/RetryStrategyTest.java index 26c3633a5b89..eeba28fc8e81 100644 --- a/test/unit/org/apache/cassandra/service/RetryStrategyTest.java +++ b/test/unit/org/apache/cassandra/service/RetryStrategyTest.java @@ -18,12 +18,19 @@ package org.apache.cassandra.service; +import java.util.Random; +import java.util.concurrent.TimeUnit; import java.util.function.IntFunction; +import java.util.function.Supplier; import org.junit.Test; import accord.utils.Gen; import accord.utils.RandomTestRunner; +import org.apache.cassandra.config.DurationSpec; +import org.apache.cassandra.config.RetrySpec; +import org.apache.cassandra.repair.SharedContext; +import org.assertj.core.api.Assertions; public class RetryStrategyTest { @@ -64,6 +71,33 @@ public void fuzzParser() }); } + @Test + public void testSeededWaitRandomizer() + { + RetrySpec spec = new RetrySpec(new RetrySpec.MaxAttempt(10), + new DurationSpec.LongMillisecondsBound("200ms"), + new DurationSpec.LongMillisecondsBound("1000ms")); + long wait1 = RetrySpec.toStrategy(sharedContext(100), spec).computeWait(1, TimeUnit.MILLISECONDS); + long wait2 = RetrySpec.toStrategy(sharedContext(100), spec).computeWait(1, TimeUnit.MILLISECONDS); + long wait3 = RetrySpec.toStrategy(sharedContext(200), spec).computeWait(1, TimeUnit.MILLISECONDS); + Assertions.assertThat(wait1).isEqualTo(wait2); + Assertions.assertThat(wait1).isNotEqualTo(wait3); + } + + private static SharedContext sharedContext(long seed) + { + return new SharedContext.ForwardingSharedContext(SharedContext.Global.instance) + { + private final Random seededRandom = new Random(seed); + + @Override + public Supplier random() + { + return () -> seededRandom; + } + }; + } + private static class TestLatencySourceFactory implements TimeoutStrategy.LatencySourceFactory {