SimpleRetryExecutor.java

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 1 1. lambda$new$0 : replaced boolean return with false for fr/sii/ogham/core/retry/SimpleRetryExecutor::lambda$new$0 → RUN_ERROR
		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 1 1. execute : negated conditional → RUN_ERROR
		if (retry == null) {
101 1 1. execute : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::execute → RUN_ERROR
			return executeWithoutRetry(actionToRetry);
102
		}
103 1 1. execute : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::execute → RUN_ERROR
		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 1 1. executeWithRetry : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::executeWithRetry → RUN_ERROR
				return actionToRetry.call();
112
			} catch (Exception e) {
113
				Instant executionFailure = Instant.now();
114 1 1. executeWithRetry : removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::handleFailure → RUN_ERROR
				handleFailure(executionStartTime, executionFailure, actionToRetry, failures, e);
115 1 1. executeWithRetry : removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::pause → RUN_ERROR
				pause(executionStartTime, executionFailure, actionToRetry, retry, e);
116
			}
117 1 1. executeWithRetry : negated conditional → RUN_ERROR
		} 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 1 1. handleFailure : negated conditional → RUN_ERROR
		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 1 1. executeWithoutRetry : replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::executeWithoutRetry → RUN_ERROR
			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 1 1. pause : removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::pauseUntil → RUN_ERROR
		pauseUntil(nextDate);
142
	}
143
144
	private void pauseUntil(Instant nextDate) throws RetryExecutionInterruptedException {
145
		try {
146 1 1. pauseUntil : removed call to fr/sii/ogham/core/async/Awaiter::waitUntil → RUN_ERROR
			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 1 1. getActionName : negated conditional → RUN_ERROR
		if (actionToRetry instanceof NamedCallable) {
154 1 1. getActionName : replaced return value with "" for fr/sii/ogham/core/retry/SimpleRetryExecutor::getActionName → RUN_ERROR
			return ((NamedCallable<?>) actionToRetry).getName();
155
		}
156 1 1. getActionName : replaced return value with "" for fr/sii/ogham/core/retry/SimpleRetryExecutor::getActionName → RUN_ERROR
		return "unnamed";
157
	}
158
159
}

Mutations

70

1.1
Location : lambda$new$0
Killed by :
replaced boolean return with false for fr/sii/ogham/core/retry/SimpleRetryExecutor::lambda$new$0 → RUN_ERROR

100

1.1
Location : execute
Killed by :
negated conditional → RUN_ERROR

101

1.1
Location : execute
Killed by :
replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::execute → RUN_ERROR

103

1.1
Location : execute
Killed by :
replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::execute → RUN_ERROR

111

1.1
Location : executeWithRetry
Killed by :
replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::executeWithRetry → RUN_ERROR

114

1.1
Location : executeWithRetry
Killed by :
removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::handleFailure → RUN_ERROR

115

1.1
Location : executeWithRetry
Killed by :
removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::pause → RUN_ERROR

117

1.1
Location : executeWithRetry
Killed by :
negated conditional → RUN_ERROR

124

1.1
Location : handleFailure
Killed by :
negated conditional → RUN_ERROR

131

1.1
Location : executeWithoutRetry
Killed by :
replaced return value with null for fr/sii/ogham/core/retry/SimpleRetryExecutor::executeWithoutRetry → RUN_ERROR

141

1.1
Location : pause
Killed by :
removed call to fr/sii/ogham/core/retry/SimpleRetryExecutor::pauseUntil → RUN_ERROR

146

1.1
Location : pauseUntil
Killed by :
removed call to fr/sii/ogham/core/async/Awaiter::waitUntil → RUN_ERROR

153

1.1
Location : getActionName
Killed by :
negated conditional → RUN_ERROR

154

1.1
Location : getActionName
Killed by :
replaced return value with "" for fr/sii/ogham/core/retry/SimpleRetryExecutor::getActionName → RUN_ERROR

156

1.1
Location : getActionName
Killed by :
replaced return value with "" for fr/sii/ogham/core/retry/SimpleRetryExecutor::getActionName → RUN_ERROR

Active mutators

Tests examined


Report generated by PIT 1.13.1