| 1 | package fr.sii.ogham.core.retry; | |
| 2 | ||
| 3 | import java.time.Instant; | |
| 4 | import java.util.ArrayList; | |
| 5 | import java.util.List; | |
| 6 | import java.util.concurrent.Callable; | |
| 7 | import java.util.function.Predicate; | |
| 8 | ||
| 9 | import org.slf4j.Logger; | |
| 10 | import org.slf4j.LoggerFactory; | |
| 11 | ||
| 12 | import fr.sii.ogham.core.async.Awaiter; | |
| 13 | import fr.sii.ogham.core.exception.async.WaitException; | |
| 14 | import fr.sii.ogham.core.exception.retry.ExecutionFailedNotRetriedException; | |
| 15 | import fr.sii.ogham.core.exception.retry.ExecutionFailureWrapper; | |
| 16 | import fr.sii.ogham.core.exception.retry.MaximumAttemptsReachedException; | |
| 17 | import fr.sii.ogham.core.exception.retry.RetryException; | |
| 18 | import fr.sii.ogham.core.exception.retry.RetryExecutionInterruptedException; | |
| 19 | import fr.sii.ogham.core.exception.retry.UnrecoverableException; | |
| 20 | ||
| 21 | /** | |
| 22 | * A simple implementation that tries to execute the action, if it fails (any | |
| 23 | * exception), it waits using {@link Thread#sleep(long)}. Once the sleep is | |
| 24 | * expired, the action is executed again. | |
| 25 | * | |
| 26 | * This process is executed until the retry strategy tells that the retries | |
| 27 | * should stop. Once stopped, it means that no execution of the action succeeded | |
| 28 | * so the last exception is thrown. | |
| 29 | * | |
| 30 | * @author Aurélien Baudet | |
| 31 | * | |
| 32 | */ | |
| 33 | public class SimpleRetryExecutor implements RetryExecutor { | |
| 34 | private static final Logger LOG = LoggerFactory.getLogger(SimpleRetryExecutor.class); | |
| 35 | ||
| 36 | /** | |
| 37 | * Use a provider in order to use a fresh {@link RetryStrategy} strategy | |
| 38 | * each time the execute method is called. This is mandatory to be able to | |
| 39 | * use the {@link RetryExecutor} in a multi-threaded application. This | |
| 40 | * avoids sharing same instance between several {@link #execute(Callable)} | |
| 41 | * calls. | |
| 42 | */ | |
| 43 | private final RetryStrategyProvider retryProvider; | |
| 44 | ||
| 45 | /** | |
| 46 | * Implementation that waits for some time between retries | |
| 47 | */ | |
| 48 | private final Awaiter awaiter; | |
| 49 | ||
| 50 | /** | |
| 51 | * Use to check if the exception is recoverable (means that a retry can be | |
| 52 | * attempted) or not (should fail immediately). | |
| 53 | */ | |
| 54 | private final Predicate<Throwable> recoverable; | |
| 55 | ||
| 56 | /** | |
| 57 | * Initializes with a provider in order to use a fresh {@link RetryStrategy} | |
| 58 | * strategy each time the execute method is called. This is mandatory to be | |
| 59 | * able to use the {@link RetryExecutor} in a multi-threaded application. | |
| 60 | * This avoids sharing same instance between several | |
| 61 | * {@link #execute(Callable)} calls. | |
| 62 | * Every exception is considered as recoverable (means that retry is attempted). | |
| 63 | * | |
| 64 | * @param retryProvider | |
| 65 | * the provider that will provide the retry strategy | |
| 66 | * @param awaiter | |
| 67 | * the waiter that waits some time between retries | |
| 68 | */ | |
| 69 | public SimpleRetryExecutor(RetryStrategyProvider retryProvider, Awaiter awaiter) { | |
| 70 |
2
1. lambda$new$0 : replaced boolean return with false for fr/sii/ogham/core/retry/SimpleRetryExecutor::lambda$new$0 → NO_COVERAGE 2. lambda$new$0 : replaced boolean return with false for fr/sii/ogham/core/retry/SimpleRetryExecutor::lambda$new$0 → KILLED |
this(retryProvider, awaiter, e -> true); |
| 71 | } | |
| 72 | ||
| 73 | /** | |
| 74 | * Initializes with a provider in order to use a fresh {@link RetryStrategy} | |
| 75 | * strategy each time the execute method is called. This is mandatory to be | |
| 76 | * able to use the {@link RetryExecutor} in a multi-threaded application. | |
| 77 | * This avoids sharing same instance between several | |
| 78 | * {@link #execute(Callable)} calls. | |
| 79 | * | |
| 80 | * @param retryProvider | |
| 81 | * the provider that will provide the retry strategy | |
| 82 | * @param awaiter | |
| 83 | * the waiter that waits some time between retries | |
| 84 | * @param recoverable | |
| 85 | * check if the exception is recoverable (means that retry can be | |
| 86 | * attempted) or unrecoverable (means that it should fail | |
| 87 | * immediately) | |
| 88 | */ | |
| 89 | public SimpleRetryExecutor(RetryStrategyProvider retryProvider, Awaiter awaiter, Predicate<Throwable> recoverable) { | |
| 90 | super(); | |
| 91 | this.retryProvider = retryProvider; | |
| 92 | this.awaiter = awaiter; | |
| 93 | this.recoverable = recoverable; | |
| 94 | } | |
| 95 | ||
| 96 | @Override | |
| 97 | public <V> V execute(Callable<V> actionToRetry) throws RetryException { | |
| 98 | // new instance for each execution | |
| 99 | RetryStrategy retry = retryProvider.provide(); | |
| 100 |
5
1. execute : negated conditional → NO_COVERAGE 2. execute : negated conditional → TIMED_OUT 3. execute : negated conditional → KILLED 4. execute : negated conditional → KILLED 5. execute : negated conditional → KILLED |
if (retry == null) { |
| 101 |
3
1. execute : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::execute → SURVIVED 2. execute : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::execute → NO_COVERAGE 3. execute : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::execute → KILLED |
return executeWithoutRetry(actionToRetry); |
| 102 | } | |
| 103 |
5
1. execute : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::execute → NO_COVERAGE 2. execute : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::execute → TIMED_OUT 3. execute : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::execute → KILLED 4. execute : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::execute → KILLED 5. execute : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::execute → KILLED |
return executeWithRetry(actionToRetry, retry); |
| 104 | } | |
| 105 | ||
| 106 | private <V> V executeWithRetry(Callable<V> actionToRetry, RetryStrategy retry) throws RetryExecutionInterruptedException, MaximumAttemptsReachedException, UnrecoverableException { | |
| 107 | List<Exception> failures = new ArrayList<>(); | |
| 108 | do { | |
| 109 | Instant executionStartTime = Instant.now(); | |
| 110 | try { | |
| 111 |
5
1. executeWithRetry : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::executeWithRetry → NO_COVERAGE 2. executeWithRetry : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::executeWithRetry → TIMED_OUT 3. executeWithRetry : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::executeWithRetry → KILLED 4. executeWithRetry : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::executeWithRetry → KILLED 5. executeWithRetry : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::executeWithRetry → KILLED |
return actionToRetry.call(); |
| 112 | } catch (Exception e) { | |
| 113 | Instant executionFailure = Instant.now(); | |
| 114 |
4
1. executeWithRetry : removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::handleFailure → NO_COVERAGE 2. executeWithRetry : removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::handleFailure → KILLED 3. executeWithRetry : removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::handleFailure → KILLED 4. executeWithRetry : removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::handleFailure → KILLED |
handleFailure(executionStartTime, executionFailure, actionToRetry, failures, e); |
| 115 |
3
1. executeWithRetry : removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::pause → NO_COVERAGE 2. executeWithRetry : removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::pause → TIMED_OUT 3. executeWithRetry : removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::pause → KILLED |
pause(executionStartTime, executionFailure, actionToRetry, retry, e); |
| 116 | } | |
| 117 |
4
1. executeWithRetry : negated conditional → NO_COVERAGE 2. executeWithRetry : negated conditional → TIMED_OUT 3. executeWithRetry : negated conditional → KILLED 4. executeWithRetry : negated conditional → KILLED |
} while (!retry.terminated()); |
| 118 | // action couldn't be executed | |
| 119 | throw new MaximumAttemptsReachedException("Maximum attempts to execute action '" + getActionName(actionToRetry) + "' is reached", failures); | |
| 120 | } | |
| 121 | ||
| 122 | private <V> void handleFailure(Instant executionStart, Instant executionFailure, Callable<V> actionToRetry, List<Exception> failures, Exception e) throws UnrecoverableException { | |
| 123 | failures.add(new ExecutionFailureWrapper(getActionName(actionToRetry), executionStart, executionFailure, e)); | |
| 124 |
4
1. handleFailure : negated conditional → NO_COVERAGE 2. handleFailure : negated conditional → KILLED 3. handleFailure : negated conditional → KILLED 4. handleFailure : negated conditional → KILLED |
if (!recoverable.test(e)) { |
| 125 | throw new UnrecoverableException("Unrecoverable exception thrown while executing '" + getActionName(actionToRetry) + "'", failures); | |
| 126 | } | |
| 127 | } | |
| 128 | ||
| 129 | private static <V> V executeWithoutRetry(Callable<V> actionToRetry) throws ExecutionFailedNotRetriedException { | |
| 130 | try { | |
| 131 |
3
1. executeWithoutRetry : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::executeWithoutRetry → NO_COVERAGE 2. executeWithoutRetry : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::executeWithoutRetry → SURVIVED 3. executeWithoutRetry : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::executeWithoutRetry → KILLED |
return actionToRetry.call(); |
| 132 | } catch (Exception e) { | |
| 133 | throw new ExecutionFailedNotRetriedException("Failed to execute action '" + getActionName(actionToRetry) + "' and no retry strategy configured", e); | |
| 134 | } | |
| 135 | } | |
| 136 | ||
| 137 | private <V> void pause(Instant executionStartTime, Instant executionFailureTime, Callable<V> actionToRetry, RetryStrategy retry, Exception e) throws RetryExecutionInterruptedException { | |
| 138 | Instant nextDate = retry.nextDate(executionStartTime, executionFailureTime); | |
| 139 | LOG.debug("{} failed ({}: {}). Retrying at {}...", getActionName(actionToRetry), e.getClass(), e.getMessage(), nextDate); | |
| 140 | LOG.trace("{}", e.getMessage(), e); | |
| 141 |
3
1. pause : removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::pauseUntil → NO_COVERAGE 2. pause : removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::pauseUntil → SURVIVED 3. pause : removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::pauseUntil → KILLED |
pauseUntil(nextDate); |
| 142 | } | |
| 143 | ||
| 144 | private void pauseUntil(Instant nextDate) throws RetryExecutionInterruptedException { | |
| 145 | try { | |
| 146 |
3
1. pauseUntil : removed call to fr/sii/ogham/core/async/Awaiter::waitUntil → NO_COVERAGE 2. pauseUntil : removed call to fr/sii/ogham/core/async/Awaiter::waitUntil → SURVIVED 3. pauseUntil : removed call to fr/sii/ogham/core/async/Awaiter::waitUntil → KILLED |
awaiter.waitUntil(nextDate); |
| 147 | } catch (WaitException e) { | |
| 148 | throw new RetryExecutionInterruptedException(e); | |
| 149 | } | |
| 150 | } | |
| 151 | ||
| 152 | private static <V> String getActionName(Callable<V> actionToRetry) { | |
| 153 |
3
1. getActionName : negated conditional → SURVIVED 2. getActionName : negated conditional → NO_COVERAGE 3. getActionName : negated conditional → KILLED |
if (actionToRetry instanceof NamedCallable) { |
| 154 |
2
1. getActionName : replaced return value with "" for fr/sii/ogham/core/retry/SimpleRetryExecutor::getActionName → NO_COVERAGE 2. getActionName : replaced return value with "" for fr/sii/ogham/core/retry/SimpleRetryExecutor::getActionName → SURVIVED |
return ((NamedCallable<?>) actionToRetry).getName(); |
| 155 | } | |
| 156 |
2
1. getActionName : replaced return value with "" for fr/sii/ogham/core/retry/SimpleRetryExecutor::getActionName → SURVIVED 2. getActionName : replaced return value with "" for fr/sii/ogham/core/retry/SimpleRetryExecutor::getActionName → NO_COVERAGE |
return "unnamed"; |
| 157 | } | |
| 158 | ||
| 159 | } | |
Mutations | ||
| 70 |
1.1 2.2 |
|
| 100 |
1.1 2.2 3.3 4.4 5.5 |
|
| 101 |
1.1 2.2 3.3 |
|
| 103 |
1.1 2.2 3.3 4.4 5.5 |
|
| 111 |
1.1 2.2 3.3 4.4 5.5 |
|
| 114 |
1.1 2.2 3.3 4.4 |
|
| 115 |
1.1 2.2 3.3 |
|
| 117 |
1.1 2.2 3.3 4.4 |
|
| 124 |
1.1 2.2 3.3 4.4 |
|
| 131 |
1.1 2.2 3.3 |
|
| 141 |
1.1 2.2 3.3 |
|
| 146 |
1.1 2.2 3.3 |
|
| 153 |
1.1 2.2 3.3 |
|
| 154 |
1.1 2.2 |
|
| 156 |
1.1 2.2 |