Skip to content

Commit 17dc2f9

Browse files
committed
Add support for Fallback.onFailedAttempt.
Fixes #248.
1 parent 1586737 commit 17dc2f9

File tree

6 files changed

+62
-14
lines changed

6 files changed

+62
-14
lines changed

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

+19-3
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@
1616
package net.jodah.failsafe;
1717

1818
import net.jodah.failsafe.event.ExecutionAttemptedEvent;
19-
import net.jodah.failsafe.function.*;
19+
import net.jodah.failsafe.function.CheckedConsumer;
20+
import net.jodah.failsafe.function.CheckedFunction;
21+
import net.jodah.failsafe.function.CheckedRunnable;
22+
import net.jodah.failsafe.function.CheckedSupplier;
23+
import net.jodah.failsafe.internal.EventListener;
2024
import net.jodah.failsafe.internal.util.Assert;
2125

2226
import java.util.concurrent.CompletableFuture;
2327
import java.util.concurrent.CompletionStage;
2428

25-
import static net.jodah.failsafe.Functions.*;
29+
import static net.jodah.failsafe.Functions.toFn;
2630

2731
/**
2832
* A Policy that handles failures using a fallback function or result.
@@ -42,6 +46,7 @@ public class Fallback<R> extends FailurePolicy<Fallback<R>, R> {
4246
private final CheckedFunction<ExecutionAttemptedEvent, R> fallback;
4347
private final CheckedFunction<ExecutionAttemptedEvent, CompletableFuture<R>> fallbackStage;
4448
private boolean async;
49+
private EventListener failedAttemptListener;
4550

4651
private Fallback() {
4752
this(null, null, false);
@@ -211,6 +216,17 @@ public boolean isAsync() {
211216
return async;
212217
}
213218

219+
/**
220+
* Registers the {@code listener} to be called when an execution attempt fails. You can also use {@link
221+
* #onFailure(CheckedConsumer) onFailure} to determine when the execution attempt fails <i>and</i> and the fallback
222+
* result fails.
223+
* <p>Note: Any exceptions that are thrown from within the {@code listener} are ignored.</p>
224+
*/
225+
public Fallback<R> onFailedAttempt(CheckedConsumer<? extends ExecutionAttemptedEvent<R>> listener) {
226+
failedAttemptListener = EventListener.ofAttempt(Assert.notNull(listener, "listener"));
227+
return this;
228+
}
229+
214230
/**
215231
* Returns the applied fallback result.
216232
*/
@@ -229,6 +245,6 @@ CompletableFuture<R> applyStage(R result, Throwable failure, ExecutionContext co
229245

230246
@Override
231247
public PolicyExecutor toExecutor(AbstractExecution execution) {
232-
return new FallbackExecutor(this, execution);
248+
return new FallbackExecutor(this, execution, failedAttemptListener);
233249
}
234250
}

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

+12-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package net.jodah.failsafe;
1717

18+
import net.jodah.failsafe.internal.EventListener;
1819
import net.jodah.failsafe.util.concurrent.Scheduler;
1920

2021
import java.util.concurrent.*;
@@ -24,8 +25,11 @@
2425
* A PolicyExecutor that handles failures according to a {@link Fallback}.
2526
*/
2627
class FallbackExecutor extends PolicyExecutor<Fallback> {
27-
FallbackExecutor(Fallback fallback, AbstractExecution execution) {
28+
private final EventListener failedAttemptListener;
29+
30+
FallbackExecutor(Fallback fallback, AbstractExecution execution, EventListener failedAttemptListener) {
2831
super(fallback, execution);
32+
this.failedAttemptListener = failedAttemptListener;
2933
}
3034

3135
/**
@@ -92,4 +96,11 @@ protected Supplier<CompletableFuture<ExecutionResult>> supplyAsync(
9296
return postExecuteAsync(result, scheduler, future);
9397
});
9498
}
99+
100+
@Override
101+
protected ExecutionResult onFailure(ExecutionResult result) {
102+
if (failedAttemptListener != null)
103+
failedAttemptListener.handle(result, execution);
104+
return result;
105+
}
95106
}

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,9 @@ public RetryPolicy<R> onAbort(CheckedConsumer<? extends ExecutionCompletedEvent<
229229
}
230230

231231
/**
232-
* Registers the {@code listener} to be called when an execution attempt fails.
232+
* Registers the {@code listener} to be called when an execution attempt fails. You can also use {@link
233+
* #onFailure(CheckedConsumer) onFailure} to determine when the execution attempt fails <i>and</i> and all retries
234+
* have failed.
233235
* <p>Note: Any exceptions that are thrown from within the {@code listener} are ignored.</p>
234236
*/
235237
public RetryPolicy<R> onFailedAttempt(CheckedConsumer<? extends ExecutionAttemptedEvent<R>> listener) {

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

+7-8
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ class RetryPolicyExecutor extends PolicyExecutor<RetryPolicy> {
4141
private volatile long delayNanos = -1;
4242

4343
// Listeners
44-
private EventListener abortListener;
45-
private EventListener failedAttemptListener;
46-
private EventListener retriesExceededListener;
47-
private EventListener retryListener;
44+
private final EventListener abortListener;
45+
private final EventListener failedAttemptListener;
46+
private final EventListener retriesExceededListener;
47+
private final EventListener retryListener;
4848

4949
RetryPolicyExecutor(RetryPolicy retryPolicy, AbstractExecution execution, EventListener abortListener,
5050
EventListener failedAttemptListener, EventListener retriesExceededListener, EventListener retryListener) {
@@ -148,6 +148,9 @@ else if (postResult != null) {
148148
@Override
149149
@SuppressWarnings("unchecked")
150150
protected ExecutionResult onFailure(ExecutionResult result) {
151+
if (failedAttemptListener != null)
152+
failedAttemptListener.handle(result, execution);
153+
151154
failedAttempts++;
152155
long waitNanos = delayNanos;
153156

@@ -175,10 +178,6 @@ protected ExecutionResult onFailure(ExecutionResult result) {
175178
boolean completed = isAbortable || !shouldRetry;
176179
boolean success = completed && result.isSuccess() && !isAbortable;
177180

178-
// Call attempt listeners
179-
if (failedAttemptListener != null && !success)
180-
failedAttemptListener.handle(result, execution);
181-
182181
// Call completion listeners
183182
if (abortListener != null && isAbortable)
184183
abortListener.handle(result, execution);

src/test/java/net/jodah/failsafe/ListenersTest.java

+8
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class ListenersTest {
3939
Waiter waiter;
4040

4141
// RetryPolicy listener counters
42+
ListenerCounter rpHandle = new ListenerCounter();
4243
ListenerCounter rpAbort = new ListenerCounter();
4344
ListenerCounter rpFailedAttempt = new ListenerCounter();
4445
ListenerCounter rpRetriesExceeded = new ListenerCounter();
@@ -54,6 +55,7 @@ public class ListenersTest {
5455
ListenerCounter cbFailure = new ListenerCounter();
5556

5657
// Fallback listener counters
58+
ListenerCounter fbFailedAttempt = new ListenerCounter();
5759
ListenerCounter fbSuccess = new ListenerCounter();
5860
ListenerCounter fbFailure = new ListenerCounter();
5961

@@ -99,6 +101,7 @@ void beforeMethod() {
99101
cbSuccess.reset();
100102
cbFailure.reset();
101103

104+
fbFailedAttempt.reset();
102105
fbSuccess.reset();
103106
fbFailure.reset();
104107

@@ -127,6 +130,7 @@ private <T> FailsafeExecutor<T> registerListeners(RetryPolicy<T> retryPolicy, Ci
127130
circuitBreaker.onFailure(e -> cbFailure.record());
128131

129132
if (fallback != null) {
133+
fallback.onFailedAttempt(e -> fbFailedAttempt.record());
130134
fallback.onSuccess(e -> fbSuccess.record());
131135
fallback.onFailure(e -> fbFailure.record());
132136
}
@@ -173,6 +177,7 @@ private void assertForSuccess(boolean sync) throws Throwable {
173177
cbSuccess.assertEquals(1);
174178
cbFailure.assertEquals(4);
175179

180+
fbFailedAttempt.assertEquals(0);
176181
fbSuccess.assertEquals(1);
177182
fbFailure.assertEquals(0);
178183

@@ -351,6 +356,7 @@ private void assertForFailingRetryPolicy(boolean sync) throws Throwable {
351356
cbSuccess.assertEquals(3);
352357
cbFailure.assertEquals(0);
353358

359+
fbFailedAttempt.assertEquals(0);
354360
fbSuccess.assertEquals(1);
355361
fbFailure.assertEquals(0);
356362

@@ -392,6 +398,7 @@ private void assertForFailingCircuitBreaker(boolean sync) throws Throwable {
392398
cbSuccess.assertEquals(0);
393399
cbFailure.assertEquals(1);
394400

401+
fbFailedAttempt.assertEquals(0);
395402
fbSuccess.assertEquals(1);
396403
fbFailure.assertEquals(0);
397404

@@ -433,6 +440,7 @@ private void assertForFailingFallback(boolean sync) throws Throwable {
433440
cbSuccess.assertEquals(1);
434441
cbFailure.assertEquals(0);
435442

443+
fbFailedAttempt.assertEquals(1);
436444
fbSuccess.assertEquals(0);
437445
fbFailure.assertEquals(1);
438446

src/test/java/net/jodah/failsafe/issues/Issue284Test.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,44 @@
77
import org.testng.annotations.Test;
88

99
import java.util.concurrent.atomic.AtomicBoolean;
10+
import java.util.concurrent.atomic.AtomicInteger;
1011

1112
import static org.testng.Assert.*;
1213

1314
@Test
1415
public class Issue284Test {
16+
AtomicInteger failedAttempt;
1517
AtomicBoolean success;
1618
AtomicBoolean failure;
1719
AtomicBoolean executed;
1820
Fallback<String> fallback;
1921
RetryPolicy<String> retryPolicy = new RetryPolicy<String>().handleResult(null)
22+
.onFailedAttempt(e -> failedAttempt.incrementAndGet())
2023
.onSuccess(e -> success.set(true))
2124
.onFailure(e -> failure.set(true));
2225

2326
@BeforeMethod
2427
protected void beforeMethod() {
28+
failedAttempt = new AtomicInteger();
2529
success = new AtomicBoolean();
2630
failure = new AtomicBoolean();
2731
executed = new AtomicBoolean();
2832
}
2933

3034
private Fallback<String> fallbackFor(String result) {
31-
return Fallback.of(result).handleResult(null).onSuccess(e -> success.set(true)).onFailure(e -> failure.set(true));
35+
return Fallback.of(result)
36+
.handleResult(null)
37+
.onFailedAttempt(e -> failedAttempt.incrementAndGet())
38+
.onSuccess(e -> success.set(true))
39+
.onFailure(e -> failure.set(true));
3240
}
3341

3442
public void testFallbackSuccess() {
3543
fallback = fallbackFor("hello");
3644
String result = Failsafe.with(fallback).get(() -> null);
3745

3846
assertEquals(result, "hello");
47+
assertEquals(failedAttempt.get(), 0);
3948
assertTrue(success.get(), "Fallback should have been successful");
4049
}
4150

@@ -44,20 +53,23 @@ public void testFallbackFailure() {
4453
String result = Failsafe.with(fallback).get(() -> null);
4554

4655
assertNull(result);
56+
assertEquals(failedAttempt.get(), 1);
4757
assertTrue(failure.get(), "Fallback should have failed");
4858
}
4959

5060
public void testRetryPolicySuccess() {
5161
String result = Failsafe.with(retryPolicy).get(() -> !executed.getAndSet(true) ? null : "hello");
5262

5363
assertEquals(result, "hello");
64+
assertEquals(failedAttempt.get(), 1);
5465
assertTrue(success.get(), "RetryPolicy should have been successful");
5566
}
5667

5768
public void testRetryPolicyFailure() {
5869
String result = Failsafe.with(retryPolicy).get(() -> null);
5970

6071
assertNull(result);
72+
assertEquals(failedAttempt.get(), 3);
6173
assertTrue(failure.get(), "RetryPolicy should have failed");
6274
}
6375
}

0 commit comments

Comments
 (0)