From 6b073186070f4f0dea50969e8344933c40e68d17 Mon Sep 17 00:00:00 2001 From: Alireza Hakimrabet Date: Fri, 24 May 2024 02:15:27 +0330 Subject: [PATCH] GH-388: add non blocking sleeper - fix (https://github.com/spring-projects/spring-retry/issues/388) - add non blocking sleeper --- .../retry/backoff/BackOffPolicyBuilder.java | 2 +- .../backoff/ExponentialBackOffPolicy.java | 2 +- .../retry/backoff/FixedBackOffPolicy.java | 4 +- .../retry/backoff/NonBlockingSleeper.java | 45 +++++++++++++++++++ .../retry/backoff/ThreadWaitSleeper.java | 2 + .../backoff/UniformRandomBackOffPolicy.java | 4 +- .../backoff/NonBlockingSleeperTests.java | 45 +++++++++++++++++++ 7 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/springframework/retry/backoff/NonBlockingSleeper.java create mode 100644 src/test/java/org/springframework/retry/backoff/NonBlockingSleeperTests.java diff --git a/src/main/java/org/springframework/retry/backoff/BackOffPolicyBuilder.java b/src/main/java/org/springframework/retry/backoff/BackOffPolicyBuilder.java index f56aa8f7..1f141a7f 100644 --- a/src/main/java/org/springframework/retry/backoff/BackOffPolicyBuilder.java +++ b/src/main/java/org/springframework/retry/backoff/BackOffPolicyBuilder.java @@ -158,7 +158,7 @@ public BackOffPolicyBuilder random(boolean random) { /** * The {@link Sleeper} instance to be used to back off. Policies default to - * {@link ThreadWaitSleeper}. + * {@link NonBlockingSleeper}. * @param sleeper the {@link Sleeper} instance * @return this */ diff --git a/src/main/java/org/springframework/retry/backoff/ExponentialBackOffPolicy.java b/src/main/java/org/springframework/retry/backoff/ExponentialBackOffPolicy.java index 25556a74..5e906662 100644 --- a/src/main/java/org/springframework/retry/backoff/ExponentialBackOffPolicy.java +++ b/src/main/java/org/springframework/retry/backoff/ExponentialBackOffPolicy.java @@ -95,7 +95,7 @@ public class ExponentialBackOffPolicy implements SleepingBackOffPolicy multiplierSupplier; - private Sleeper sleeper = new ThreadWaitSleeper(); + private Sleeper sleeper = new NonBlockingSleeper(); /** * Public setter for the {@link Sleeper} strategy. diff --git a/src/main/java/org/springframework/retry/backoff/FixedBackOffPolicy.java b/src/main/java/org/springframework/retry/backoff/FixedBackOffPolicy.java index fc26986a..e9d66fcd 100644 --- a/src/main/java/org/springframework/retry/backoff/FixedBackOffPolicy.java +++ b/src/main/java/org/springframework/retry/backoff/FixedBackOffPolicy.java @@ -45,7 +45,7 @@ public class FixedBackOffPolicy extends StatelessBackOffPolicy implements Sleepi */ private Supplier backOffPeriod = () -> DEFAULT_BACK_OFF_PERIOD; - private Sleeper sleeper = new ThreadWaitSleeper(); + private Sleeper sleeper = new NonBlockingSleeper(); public FixedBackOffPolicy withSleeper(Sleeper sleeper) { FixedBackOffPolicy res = new FixedBackOffPolicy(); @@ -56,7 +56,7 @@ public FixedBackOffPolicy withSleeper(Sleeper sleeper) { /** * Public setter for the {@link Sleeper} strategy. - * @param sleeper the sleeper to set defaults to {@link ThreadWaitSleeper}. + * @param sleeper the sleeper to set defaults to {@link NonBlockingSleeper}. */ public void setSleeper(Sleeper sleeper) { this.sleeper = sleeper; diff --git a/src/main/java/org/springframework/retry/backoff/NonBlockingSleeper.java b/src/main/java/org/springframework/retry/backoff/NonBlockingSleeper.java new file mode 100644 index 00000000..5465af05 --- /dev/null +++ b/src/main/java/org/springframework/retry/backoff/NonBlockingSleeper.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.retry.backoff; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * Non-blocking {@link Sleeper} implementation that just sleep with specified + * backOffPeriod. + * + * @author Alireza Hakimrabet + */ +@SuppressWarnings("serial") +public class NonBlockingSleeper implements Sleeper { + + @Override + public void sleep(long backOffPeriod) throws InterruptedException { + CompletableFuture future = CompletableFuture.runAsync(() -> { + try { + TimeUnit.MILLISECONDS.sleep(backOffPeriod); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Unexpected exception", e); + } + }); + future.join(); + } + +} diff --git a/src/main/java/org/springframework/retry/backoff/ThreadWaitSleeper.java b/src/main/java/org/springframework/retry/backoff/ThreadWaitSleeper.java index 93a28df0..76653e6a 100644 --- a/src/main/java/org/springframework/retry/backoff/ThreadWaitSleeper.java +++ b/src/main/java/org/springframework/retry/backoff/ThreadWaitSleeper.java @@ -20,9 +20,11 @@ * Simple {@link Sleeper} implementation that just blocks the current Thread with sleep * period. * + * @deprecated in favor of {@link NonBlockingSleeper} * @author Artem Bilan * @since 1.1 */ +@Deprecated @SuppressWarnings("serial") public class ThreadWaitSleeper implements Sleeper { diff --git a/src/main/java/org/springframework/retry/backoff/UniformRandomBackOffPolicy.java b/src/main/java/org/springframework/retry/backoff/UniformRandomBackOffPolicy.java index 68249f7b..f135e10d 100644 --- a/src/main/java/org/springframework/retry/backoff/UniformRandomBackOffPolicy.java +++ b/src/main/java/org/springframework/retry/backoff/UniformRandomBackOffPolicy.java @@ -53,7 +53,7 @@ public class UniformRandomBackOffPolicy extends StatelessBackOffPolicy private final Random random = new Random(System.currentTimeMillis()); - private Sleeper sleeper = new ThreadWaitSleeper(); + private Sleeper sleeper = new NonBlockingSleeper(); public UniformRandomBackOffPolicy withSleeper(Sleeper sleeper) { UniformRandomBackOffPolicy res = new UniformRandomBackOffPolicy(); @@ -65,7 +65,7 @@ public UniformRandomBackOffPolicy withSleeper(Sleeper sleeper) { /** * Public setter for the {@link Sleeper} strategy. - * @param sleeper the sleeper to set defaults to {@link ThreadWaitSleeper}. + * @param sleeper the sleeper to set defaults to {@link NonBlockingSleeper}. */ public void setSleeper(Sleeper sleeper) { this.sleeper = sleeper; diff --git a/src/test/java/org/springframework/retry/backoff/NonBlockingSleeperTests.java b/src/test/java/org/springframework/retry/backoff/NonBlockingSleeperTests.java new file mode 100644 index 00000000..202f2ef2 --- /dev/null +++ b/src/test/java/org/springframework/retry/backoff/NonBlockingSleeperTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2006-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.retry.backoff; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Alireza Hakimrabet + */ +public class NonBlockingSleeperTests { + + @Test + public void testSingleBackOff() throws Exception { + long backOffPeriod = 50; + NonBlockingSleeper strategy = new NonBlockingSleeper(); + long before = System.currentTimeMillis(); + strategy.sleep(backOffPeriod); + long after = System.currentTimeMillis(); + assertEqualsApprox(backOffPeriod, after - before, 25); + } + + private void assertEqualsApprox(long desired, long actual, long variance) { + long lower = desired - variance; + long upper = desired + 2 * variance; + assertThat(lower).describedAs("Expected value to be between '%d' and '%d' but was '%d'", lower, upper, actual) + .isLessThanOrEqualTo(actual); + } + +}