Skip to content

Commit 9082546

Browse files
committed
Large test refactorings and improvements
- Moved most test cases towards being more functional tests, that test broader behaviors/scenarios - Different test scenarios are more clearly separated - Most tests are now run against every type of execution, including sync and all the async varieties - Improved utilties for running tests - Removed a lot of the original test execution code
1 parent 82aad04 commit 9082546

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2093
-2467
lines changed

pom.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,20 @@
8888
<target>1.8</target>
8989
</configuration>
9090
</plugin>
91+
<plugin>
92+
<groupId>org.apache.maven.plugins</groupId>
93+
<artifactId>maven-surefire-plugin</artifactId>
94+
<version>2.22.2</version>
95+
<configuration>
96+
<trimStackTrace>false</trimStackTrace>
97+
<properties>
98+
<property>
99+
<name>listener</name>
100+
<value>net.jodah.failsafe.testing.TestCaseLogger</value>
101+
</property>
102+
</properties>
103+
</configuration>
104+
</plugin>
91105
<plugin>
92106
<artifactId>maven-release-plugin</artifactId>
93107
<configuration>

src/main/java/net/jodah/failsafe/Failsafe.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public static <R, P extends Policy<R>> FailsafeExecutor<R> with(P outerPolicy, P
8888
* @param <P> policy type
8989
* @throws NullPointerException if {@code policies} is null
9090
* @throws IllegalArgumentException if {@code policies} is empty
91-
* @deprecated Use {@link #with(Policy, Policy[])} instead
91+
* @deprecated This will be removed in 3.0. Use {@link #with(Policy, Policy[])} instead
9292
*/
9393
@Deprecated
9494
public static <R, P extends Policy<R>> FailsafeExecutor<R> with(P[] policies) {

src/main/java/net/jodah/failsafe/FailsafeExecutor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,10 @@ public <T extends R> CompletableFuture<T> getStageAsync(
247247
*
248248
* @throws NullPointerException if the {@code supplier} is null
249249
* @throws RejectedExecutionException if the {@code supplier} cannot be scheduled for execution
250+
* @deprecated This will be removed in 3.0. Use either {@link #getStageAsync(ContextualSupplier) getStageAsync} or
251+
* {@link #getAsyncExecution(AsyncRunnable) getAsyncExecution} instead
250252
*/
253+
@Deprecated
251254
public <T extends R> CompletableFuture<T> getStageAsyncExecution(
252255
AsyncSupplier<T, ? extends CompletionStage<T>> supplier) {
253256
return callAsync(future -> getPromiseOfStageExecution(supplier, future), true);

src/main/java/net/jodah/failsafe/Functions.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,10 @@ static <R> Function<AsyncExecutionInternal<R>, CompletableFuture<ExecutionResult
160160
* supplier cannot be applied multiple times concurrently.
161161
*
162162
* @param <R> result type
163+
* @deprecated This will be removed in 3.0.
163164
*/
164165
@SuppressWarnings("unchecked")
166+
@Deprecated
165167
static <R> Function<AsyncExecutionInternal<R>, CompletableFuture<ExecutionResult<R>>> getPromiseOfStageExecution(
166168
AsyncSupplier<R, ? extends CompletionStage<? extends R>> supplier, FailsafeFuture<R> future) {
167169

@@ -177,10 +179,10 @@ static <R> Function<AsyncExecutionInternal<R>, CompletableFuture<ExecutionResult
177179
if (stage instanceof Future)
178180
future.propagateCancellation((Future<R>) stage);
179181

180-
stage.whenComplete((innerResult, failure) -> {
182+
stage.whenComplete((result, failure) -> {
181183
try {
182184
if (failure != null)
183-
execution.record(innerResult, failure instanceof CompletionException ? failure.getCause() : failure);
185+
execution.record(result, failure instanceof CompletionException ? failure.getCause() : failure);
184186
} finally {
185187
asyncFutureLock.release();
186188
}
@@ -244,7 +246,7 @@ static <R> Function<AsyncExecutionInternal<R>, CompletableFuture<ExecutionResult
244246
Future<?> scheduledFuture = scheduler.schedule(callable, 0, TimeUnit.NANOSECONDS);
245247

246248
// Propagate outer cancellations to the scheduled innerFn and its promise
247-
future.setCancelFn((mayInterrupt, cancelResult) -> {
249+
future.setCancelFn(-1, (mayInterrupt, cancelResult) -> {
248250
scheduledFuture.cancel(mayInterrupt);
249251

250252
// Cancel a pending promise if the execution attempt has not started

src/main/java/net/jodah/failsafe/RetryPolicyExecutor.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,11 @@ public Object handleAsync(AsyncExecutionInternal<R> execution,
157157
previousResultRef);
158158
Future<?> scheduledRetry = scheduler.schedule(retryFn, postResult.getDelay(),
159159
TimeUnit.NANOSECONDS);
160+
// Cancel prior inner executions, such as pending timeouts
161+
future.cancelDependencies(this, false, null);
162+
160163
// Propagate outer cancellations to the thread that the innerFn will run with
161-
future.setCancelFn((mayInterrupt, cancelResult) -> {
164+
future.setCancelFn(-1, (mayInterrupt, cancelResult) -> {
162165
scheduledRetry.cancel(mayInterrupt);
163166
});
164167

src/main/java/net/jodah/failsafe/Timeout.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ public Duration getTimeout() {
4949
* This method is deprecated and will be removed in a future minor release.
5050
*
5151
* @see #withCancel(boolean)
52-
* @deprecated Always returns {@code true}
52+
* @deprecated This will be removed in 3.0. Always returns {@code true}.
5353
*/
54+
@Deprecated
5455
public boolean canCancel() {
5556
return true;
5657
}
@@ -69,9 +70,10 @@ public boolean canInterrupt() {
6970
* Use {@link #withInterrupt(boolean)} instead if you wish to set interruption.
7071
*
7172
* @see #withInterrupt(boolean)
72-
* @deprecated Tasks are cancelled by default when a Timeout is exceeded. Use {@link #withInterrupt(boolean)}
73+
* @deprecated This will be removed in 3.0. Tasks are cancelled by default when a Timeout is exceeded. Use {@link #withInterrupt(boolean)}
7374
* interrupt cancelled tasks when a Timeout is exceeded.
7475
*/
76+
@Deprecated
7577
public Timeout<R> withCancel(boolean mayInterruptIfRunning) {
7678
interruptable = mayInterruptIfRunning;
7779
return this;

src/main/java/net/jodah/failsafe/TimeoutExecutor.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package net.jodah.failsafe;
1717

1818
import net.jodah.failsafe.spi.*;
19-
import net.jodah.failsafe.spi.Scheduler;
2019

2120
import java.util.concurrent.CompletableFuture;
2221
import java.util.concurrent.Future;
@@ -131,10 +130,10 @@ public Function<AsyncExecutionInternal<R>, CompletableFuture<ExecutionResult<R>>
131130
}, policy.getTimeout().toNanos(), TimeUnit.NANOSECONDS);
132131
timeoutFutureRef.set(timeoutFuture);
133132

134-
// Propagate outer cancellations to the Timeout future
133+
// Propagate outer cancellations to the Timeout future and its promise
135134
future.setCancelFn(this, (mayInterrupt, cancelResult) -> {
136-
resultRef.compareAndSet(null, cancelResult);
137135
timeoutFuture.cancel(mayInterrupt);
136+
resultRef.compareAndSet(null, cancelResult);
138137
});
139138
} catch (Throwable t) {
140139
// Hard scheduling failure
@@ -155,13 +154,14 @@ public Function<AsyncExecutionInternal<R>, CompletableFuture<ExecutionResult<R>>
155154
if (!resultRef.compareAndSet(null, result))
156155
result = resultRef.get();
157156

158-
// Cancel timeout task
159-
Future<R> timeoutFuture = timeoutFutureRef.get();
160-
if (timeoutFuture != null && !timeoutFuture.isDone())
161-
timeoutFuture.cancel(false);
157+
if (result != null) {
158+
// Cancel timeout task
159+
Future<R> timeoutFuture = timeoutFutureRef.get();
160+
if (timeoutFuture != null && !timeoutFuture.isDone())
161+
timeoutFuture.cancel(false);
162162

163-
if (result != null)
164163
postExecuteAsync(execution, result, scheduler, future);
164+
}
165165

166166
promise.complete(result);
167167
});

src/main/java/net/jodah/failsafe/spi/ExecutionResult.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ public ExecutionResult<R> with(long delayNanos, boolean complete, boolean succes
189189
}
190190

191191
/**
192-
* Returns whether all execution results leading to this result have been successful.
192+
* Returns whether the execution was successful for all policies.
193193
*/
194194
public boolean getSuccessAll() {
195195
return successAll != null && successAll;
@@ -215,8 +215,4 @@ public boolean equals(Object o) {
215215
public int hashCode() {
216216
return Objects.hash(result, failure);
217217
}
218-
219-
String toSummary() {
220-
return "[result=" + result + ", failure=" + failure + "]";
221-
}
222218
}

src/main/java/net/jodah/failsafe/spi/FailsafeFuture.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
*/
1616
package net.jodah.failsafe.spi;
1717

18-
import java.util.HashMap;
18+
import java.util.Collections;
1919
import java.util.Iterator;
2020
import java.util.Map;
2121
import java.util.Map.Entry;
22+
import java.util.TreeMap;
2223
import java.util.concurrent.CancellationException;
2324
import java.util.concurrent.CompletableFuture;
2425
import java.util.concurrent.Future;
@@ -35,9 +36,9 @@ public class FailsafeFuture<R> extends CompletableFuture<R> {
3536

3637
// Mutable state guarded by "this"
3738

38-
// The most recent executoin attempt
39+
// The most recent execution attempt
3940
private ExecutionInternal<R> newestExecution;
40-
// Functions to apply when this future is cancelled
41+
// Functions to apply when this future is cancelled for each policy index, in descending order
4142
private Map<Integer, BiConsumer<Boolean, ExecutionResult<R>>> cancelFunctions;
4243
// Whether a cancel with interrupt has already occurred
4344
private boolean cancelledWithInterrupt;
@@ -111,6 +112,10 @@ public synchronized void cancelDependencies(PolicyExecutor<R, ?> cancellingPolic
111112
int cancellingPolicyIndex =
112113
cancellingPolicyExecutor == null ? Integer.MAX_VALUE : cancellingPolicyExecutor.getPolicyIndex();
113114
Iterator<Entry<Integer, BiConsumer<Boolean, ExecutionResult<R>>>> it = cancelFunctions.entrySet().iterator();
115+
116+
/* This iteration occurs in descending order to ensure that the {@code cancelResult} can be supplied to outer
117+
cancel functions before the inner supplier is cancelled, which would cause PolicyExecutors to complete with
118+
CancellationException rather than the expected {@code cancelResult}. */
114119
while (it.hasNext()) {
115120
Map.Entry<Integer, BiConsumer<Boolean, ExecutionResult<R>>> entry = it.next();
116121
if (cancellingPolicyIndex > entry.getKey()) {
@@ -134,12 +139,13 @@ public synchronized void setExecution(ExecutionInternal<R> execution) {
134139

135140
/**
136141
* Sets a {@code cancelFn} to be called when a PolicyExecutor {@link #cancelDependencies(PolicyExecutor, boolean,
137-
* ExecutionResult) cancels dependencies} or when this future is {@link #cancel(boolean) cancelled}.
142+
* ExecutionResult) cancels dependencies} with a policyIndex > the given {@code policyIndex}, or when this future is
143+
* {@link #cancel(boolean) cancelled}.
138144
*/
139-
public synchronized void setCancelFn(BiConsumer<Boolean, ExecutionResult<R>> cancelFn) {
145+
public synchronized void setCancelFn(int policyIndex, BiConsumer<Boolean, ExecutionResult<R>> cancelFn) {
140146
if (cancelFunctions == null)
141-
cancelFunctions = new HashMap<>();
142-
cancelFunctions.put(-1, cancelFn);
147+
cancelFunctions = new TreeMap<>(Collections.reverseOrder());
148+
cancelFunctions.put(policyIndex, cancelFn);
143149
}
144150

145151
/**
@@ -150,7 +156,7 @@ public synchronized void setCancelFn(BiConsumer<Boolean, ExecutionResult<R>> can
150156
public synchronized void setCancelFn(PolicyExecutor<R, ?> policyExecutor,
151157
BiConsumer<Boolean, ExecutionResult<R>> cancelFn) {
152158
if (cancelFunctions == null)
153-
cancelFunctions = new HashMap<>();
159+
cancelFunctions = new TreeMap<>(Collections.reverseOrder());
154160
cancelFunctions.put(policyExecutor.getPolicyIndex(), cancelFn);
155161
}
156162

@@ -162,6 +168,6 @@ public synchronized void propagateCancellation(Future<R> future) {
162168
if (isCancelled())
163169
future.cancel(cancelledWithInterrupt);
164170
else
165-
setCancelFn((mayInterrupt, cancelResult) -> future.cancel(mayInterrupt));
171+
setCancelFn(-2, (mayInterrupt, cancelResult) -> future.cancel(mayInterrupt));
166172
}
167173
}

src/main/java/net/jodah/failsafe/spi/PolicyExecutor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ protected synchronized CompletableFuture<ExecutionResult<R>> postExecuteAsync(As
126126
ExecutionResult<R> result, Scheduler scheduler, FailsafeFuture<R> future) {
127127
CompletableFuture<ExecutionResult<R>> postFuture = null;
128128

129-
// Guard against post executing twice for the same execution. This will happen if one async execution result
130-
// is recorded by a timeout and another asynchronously.
129+
/* Guard against post executing twice for the same execution. This will happen if one async execution result is
130+
* recorded by a timeout and another via AsyncExecution.record. */
131131
if (!execution.isAsyncExecution() || !execution.isPostExecuted(policyIndex)) {
132132
execution.recordAttempt();
133133
if (isFailure(result)) {

0 commit comments

Comments
 (0)