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 |