Skip to content

Commit

Permalink
Add checked predicates, clarify the meaning of failure vs exception
Browse files Browse the repository at this point in the history
This commit adds checked predicates and bi-predicates to config builders. This extra lenience will make it easier to build policies, and always should have been supported since exceptions from predicates are ignored anyways.

This commit also clarifies the meaning of "failure" vs "exception". Unfortunately, "failure" has been very overloaded in Failsafe to mean an exception or something that a policy actually considers a failure, which may or may not be a particular exception.

This commit deprecates many of the fields that record or get "failures" where failure is really meant to mean an exception, and adds new methods with "exception" naming.
  • Loading branch information
jhalterman committed Feb 10, 2022
1 parent 94d85aa commit 86fffa9
Show file tree
Hide file tree
Showing 58 changed files with 526 additions and 386 deletions.
14 changes: 10 additions & 4 deletions src/main/java/dev/failsafe/AsyncExecution.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ public interface AsyncExecution<R> extends ExecutionContext<R> {
boolean isComplete();

/**
* Records an execution {@code result} or {@code failure} which triggers failure handling, if needed, by the
* Records an execution {@code result} or {@code exception} which triggers failure handling, if needed, by the
* configured policies. If policy handling is not possible or already complete, the resulting {@link
* CompletableFuture} is completed.
*
* @throws IllegalStateException if the most recent execution was already recorded or the execution is complete
*/
void record(R result, Throwable failure);
void record(R result, Throwable exception);

/**
* Records an execution {@code result} which triggers failure handling, if needed, by the configured policies. If
Expand All @@ -55,10 +55,16 @@ public interface AsyncExecution<R> extends ExecutionContext<R> {
void recordResult(R result);

/**
* Records an execution {@code failure} which triggers failure handling, if needed, by the configured policies. If
* policy handling is not possible or already complete, the resulting {@link CompletableFuture} is completed.
* Records an {@code exception} which triggers failure handling, if needed, by the configured policies. If policy
* handling is not possible or already complete, the resulting {@link CompletableFuture} is completed.
*
* @throws IllegalStateException if the most recent execution was already recorded or the execution is complete
*/
void recordException(Throwable exception);

/**
* @deprecated Use {@link #recordException(Throwable)} instead
*/
@Deprecated
void recordFailure(Throwable failure);
}
14 changes: 10 additions & 4 deletions src/main/java/dev/failsafe/AsyncExecutionImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ public boolean isComplete() {
}

@Override
public void record(R result, Throwable failure) {
public void record(R result, Throwable exception) {
Assert.state(!recorded, "The most recent execution has already been recorded or completed");
recorded = true;

// Guard against race with a timeout expiring
synchronized (future) {
if (!attemptRecorded) {
Assert.state(!completed, "Execution has already been completed");
record(new ExecutionResult<>(result, failure));
record(new ExecutionResult<>(result, exception));
}

// Proceed with handling the recorded result
Expand All @@ -107,8 +107,14 @@ public void recordResult(R result) {
}

@Override
public void recordException(Throwable exception) {
record(null, exception);
}

@Override
@Deprecated
public void recordFailure(Throwable failure) {
record(null, failure);
recordException(failure);
}

@Override
Expand Down Expand Up @@ -154,7 +160,7 @@ private void complete(ExecutionResult<R> result, Throwable error) {
else {
if (error instanceof CompletionException)
error = error.getCause();
future.completeResult(ExecutionResult.failure(error));
future.completeResult(ExecutionResult.exception(error));
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/dev/failsafe/BulkheadBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public Bulkhead<R> build() {
/**
* Configures the {@code maxWaitTime} to wait for permits to be available. If permits cannot be acquired before the
* {@code maxWaitTime} is exceeded, then the bulkhead will throw {@link BulkheadFullException}.
* <p>
* This setting only applies when the resulting Bulkhead is used with the {@link Failsafe} class. It does not apply
* when the Bulkhead is used in a standalone way.
* </p>
*
* @throws NullPointerException if {@code maxWaitTime} is null
*/
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/dev/failsafe/BulkheadConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public int getMaxConcurrency() {
/**
* Returns the max time to wait for permits to be available. If permits cannot be acquired before the max wait time is
* exceeded, then the bulkhead will throw {@link BulkheadFullException}.
* <p>
* This setting only applies when the Bulkhead is used with the {@link Failsafe} class. It does not apply when the
* Bulkhead is used in a standalone way.
* </p>
*
* @see BulkheadBuilder#withMaxWaitTime(Duration)
*/
Expand Down
12 changes: 9 additions & 3 deletions src/main/java/dev/failsafe/CircuitBreaker.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ enum State {
* to the configured success or failure thresholding capacity.
* @see #tryAcquirePermit()
* @see #recordResult(Object)
* @see #recordFailure(Throwable)
* @see #recordException(Throwable)
* @see #recordSuccess()
* @see #recordFailure()
*/
Expand All @@ -126,7 +126,7 @@ default void acquirePermit() {
* automatically released when a result or failure is recorded.
*
* @see #recordResult(Object)
* @see #recordFailure(Throwable)
* @see #recordException(Throwable)
* @see #recordSuccess()
* @see #recordFailure()
*/
Expand Down Expand Up @@ -229,8 +229,14 @@ default void acquirePermit() {
void recordFailure();

/**
* Records an execution {@code failure} as a success or failure based on the failure configuration.
* Records an {@code exception} as a success or failure based on the exception configuration.
*/
void recordException(Throwable exception);

/**
* @deprecated Use {@link #recordException(Throwable)} instead.
*/
@Deprecated
void recordFailure(Throwable failure);

/**
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/dev/failsafe/CircuitBreakerBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,23 @@

import dev.failsafe.event.CircuitBreakerStateChangedEvent;
import dev.failsafe.event.EventListener;
import dev.failsafe.function.CheckedBiPredicate;
import dev.failsafe.function.CheckedPredicate;
import dev.failsafe.internal.CircuitBreakerImpl;
import dev.failsafe.internal.util.Assert;
import dev.failsafe.internal.util.Durations;

import java.time.Duration;
import java.util.function.BiPredicate;
import java.util.function.Predicate;

/**
* Builds {@link CircuitBreaker} instances.
* <ul>
* <li>By default, any exception is considered a failure and will be handled by the policy. You can override this by
* specifying your own {@code handle} conditions. The default exception handling condition will only be overridden by
* another condition that handles failure exceptions such as {@link #handle(Class)} or {@link #handleIf(BiPredicate)}.
* another condition that handles exceptions such as {@link #handle(Class)} or {@link #handleIf(CheckedBiPredicate)}.
* Specifying a condition that only handles results, such as {@link #handleResult(Object)} or
* {@link #handleResultIf(Predicate)} will not replace the default exception handling condition.</li>
* <li>If multiple {@code handle} conditions are specified, any condition that matches an execution result or failure
* {@link #handleResultIf(CheckedPredicate)} will not replace the default exception handling condition.</li>
* <li>If multiple {@code handle} conditions are specified, any condition that matches an execution result or exception
* will trigger policy handling.</li>
* </ul>
* <p>
Expand Down
20 changes: 10 additions & 10 deletions src/main/java/dev/failsafe/DelayablePolicyBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public S withDelay(Duration delay) {
* <li>Any configured jitter is still applied to DelayFunction provided values
* <li>Any configured max duration is still applied to DelayFunction provided values
* <li>The {@link ExecutionContext} that is provided to the {@code delayFunction} may be {@code null} if the prior execution
* failure was manually recorded outside of a Failsafe execution.</li>
* exception was manually recorded outside of a Failsafe execution.</li>
* </ul>
* </p>
*
Expand All @@ -78,7 +78,7 @@ public S withDelayFn(ContextualSupplier<R, Duration> delayFunction) {

/**
* Sets the {@code delayFunction} that computes the next delay before allowing another execution. Delays will only
* occur for failures that are assignable from the {@code failure}.
* occur for exceptions that are assignable from the {@code exception}.
* <p>
* The {@code delayFunction} must complete quickly, not have side-effects, and always return the same result for the
* same input. Exceptions thrown by the {@code delayFunction} method will <strong>not</strong> be handled and will
Expand All @@ -91,20 +91,20 @@ public S withDelayFn(ContextualSupplier<R, Duration> delayFunction) {
* <li>Any configured jitter is still applied to DelayFunction provided values
* <li>Any configured max duration is still applied to DelayFunction provided values
* <li>The {@link ExecutionContext} that is provided to the {@code delayFunction} may be {@code null} if the prior execution
* failure was manually recorded outside of a Failsafe execution.</li>
* exception was manually recorded outside of a Failsafe execution.</li>
* </ul>
* </p>
*
* @param delayFunction the function to use to compute the delay before a next attempt
* @param failure the execution failure that is expected in order to trigger the delay
* @param <F> failure type
* @throws NullPointerException if {@code delayFunction} or {@code failure} are null
* @param exception the execution exception that is expected in order to trigger the delay
* @param <F> exception type
* @throws NullPointerException if {@code delayFunction} or {@code exception} are null
*/
@SuppressWarnings("unchecked")
public <F extends Throwable> S withDelayFnOn(ContextualSupplier<R, Duration> delayFunction, Class<F> failure) {
public <F extends Throwable> S withDelayFnOn(ContextualSupplier<R, Duration> delayFunction, Class<F> exception) {
withDelayFn(delayFunction);
Assert.notNull(failure, "failure");
config.delayFailure = failure;
Assert.notNull(exception, "exception");
config.delayException = exception;
return (S) this;
}

Expand All @@ -123,7 +123,7 @@ public <F extends Throwable> S withDelayFnOn(ContextualSupplier<R, Duration> del
* <li>Any configured jitter is still applied to DelayFunction provided values
* <li>Any configured max duration is still applied to DelayFunction provided values
* <li>The {@link ExecutionContext} that is provided to the {@code delayFunction} may be {@code null} if the prior execution
* failure was manually recorded outside of a Failsafe execution.</li>
* exception was manually recorded outside of a Failsafe execution.</li>
* </ul>
* </p>
*
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/dev/failsafe/DelayablePolicyConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
public abstract class DelayablePolicyConfig<R> extends FailurePolicyConfig<R> {
Duration delay;
R delayResult;
Class<? extends Throwable> delayFailure;
Class<? extends Throwable> delayException;
ContextualSupplier<R, Duration> delayFn;

protected DelayablePolicyConfig() {
Expand All @@ -38,7 +38,7 @@ protected DelayablePolicyConfig(DelayablePolicyConfig<R> config) {
super(config);
delay = config.delay;
delayResult = config.delayResult;
delayFailure = config.delayFailure;
delayException = config.delayException;
delayFn = config.delayFn;
}

Expand Down Expand Up @@ -67,8 +67,8 @@ public ContextualSupplier<R, Duration> getDelayFn() {
*
* @see DelayablePolicyBuilder#withDelayFnOn(ContextualSupplier, Class)
*/
public Class<? extends Throwable> getDelayFailure() {
return delayFailure;
public Class<? extends Throwable> getDelayException() {
return delayException;
}

/**
Expand Down
14 changes: 10 additions & 4 deletions src/main/java/dev/failsafe/Execution.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ static <R> Execution<R> of(Policy<R> outerPolicy, Policy<R>... policies) {
Duration getDelay();

/**
* Records an execution {@code result} or {@code failure} which triggers failure handling, if needed, by the
* Records an execution {@code result} or {@code exception} which triggers failure handling, if needed, by the
* configured policies. If policy handling is not possible or completed, the execution is completed.
*
* @throws IllegalStateException if the execution is already complete
*/
void record(R result, Throwable failure);
void record(R result, Throwable exception);

/**
* Records an execution {@code result} which triggers failure handling, if needed, by the configured policies. If
Expand All @@ -75,10 +75,16 @@ static <R> Execution<R> of(Policy<R> outerPolicy, Policy<R>... policies) {
void recordResult(R result);

/**
* Records an execution {@code failure} which triggers failure handling, if needed, by the configured policies. If
* policy handling is not possible or completed, the execution is completed.
* Records an {@code exception} which triggers failure handling, if needed, by the configured policies. If policy
* handling is not possible or completed, the execution is completed.
*
* @throws IllegalStateException if the execution is already complete
*/
void recordException(Throwable exception);

/**
* @deprecated Use {@link #recordException(Throwable)} instead
*/
@Deprecated
void recordFailure(Throwable failure);
}
8 changes: 7 additions & 1 deletion src/main/java/dev/failsafe/ExecutionContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,14 @@ public interface ExecutionContext<R> {
int getExecutionCount();

/**
* Returns the last failure that was recorded else {@code null}.
* Returns the last exception that was recorded else {@code null}.
*/
<T extends Throwable> T getLastException();

/**
* @deprecated Use {@link #getLastException()} instead
*/
@Deprecated
<T extends Throwable> T getLastFailure();

/**
Expand Down
12 changes: 9 additions & 3 deletions src/main/java/dev/failsafe/ExecutionImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,15 @@ public int getExecutionCount() {

@Override
@SuppressWarnings("unchecked")
public <T extends Throwable> T getLastFailure() {
public <T extends Throwable> T getLastException() {
ExecutionResult<R> r = result != null ? result : previousResult;
return r == null ? null : (T) r.getFailure();
return r == null ? null : (T) r.getException();
}

@Override
@Deprecated
public <T extends Throwable> T getLastFailure() {
return getLastException();
}

@Override
Expand Down Expand Up @@ -230,6 +236,6 @@ public boolean isRetry() {
@Override
public String toString() {
return "[" + "attempts=" + attempts + ", executions=" + executions + ", lastResult=" + getLastResult()
+ ", lastFailure=" + getLastFailure() + ']';
+ ", lastException=" + getLastException() + ']';
}
}
Loading

0 comments on commit 86fffa9

Please sign in to comment.