Skip to content

Commit

Permalink
Add non-wrapped exceptions to get()
Browse files Browse the repository at this point in the history
  • Loading branch information
Gaibhne committed Feb 10, 2020
1 parent 7f411c6 commit 1dc0c8d
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 11 deletions.
16 changes: 13 additions & 3 deletions src/main/java/net/jodah/failsafe/Execution.java
Expand Up @@ -15,12 +15,12 @@
*/
package net.jodah.failsafe;

import net.jodah.failsafe.internal.util.Assert;
import net.jodah.failsafe.internal.util.DelegatingScheduler;

import java.util.Arrays;
import java.util.function.Supplier;

import net.jodah.failsafe.internal.util.Assert;
import net.jodah.failsafe.internal.util.DelegatingScheduler;

/**
* Tracks executions and determines when an execution can be performed for a {@link RetryPolicy}.
*
Expand Down Expand Up @@ -131,4 +131,14 @@ ExecutionResult executeSync(Supplier<ExecutionResult> supplier) {
executor.handleComplete(result, this);
return result;
}

<E extends Throwable> ExecutionResultWithException<E> executeSyncWithException(Supplier<ExecutionResultWithException<E>> supplier) {
for (PolicyExecutor<Policy<Object>> policyExecutor : policyExecutors)
supplier = policyExecutor.supplyWithException(supplier, scheduler);

ExecutionResultWithException<E> result = supplier.get();
completed = result.isComplete();
executor.handleComplete(result, this);
return result;
}
}
2 changes: 1 addition & 1 deletion src/main/java/net/jodah/failsafe/ExecutionResult.java
Expand Up @@ -50,7 +50,7 @@ public ExecutionResult(Object result, Throwable failure) {
this(result, failure, false, 0, false, failure == null, failure == null);
}

private ExecutionResult(Object result, Throwable failure, boolean nonResult, long waitNanos, boolean complete,
protected ExecutionResult(Object result, Throwable failure, boolean nonResult, long waitNanos, boolean complete,
boolean success, Boolean successAll) {
this.nonResult = nonResult;
this.result = result;
Expand Down
127 changes: 127 additions & 0 deletions src/main/java/net/jodah/failsafe/ExecutionResultWithException.java
@@ -0,0 +1,127 @@
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package net.jodah.failsafe;

import java.util.Objects;

/**
* The result of an execution. Immutable.
* <p>
* Part of the Failsafe SPI.
*
* @author Jonathan Halterman
*/
public class ExecutionResultWithException<E extends Throwable> extends ExecutionResult {
public ExecutionResultWithException(Object result, E failure) {
this(result, failure, false, 0, false, failure == null, failure == null);
}

private ExecutionResultWithException(Object result, E failure, boolean nonResult, long waitNanos, boolean complete,
boolean success, Boolean successAll) {
super(result, failure, nonResult, waitNanos, complete, success, successAll);
}

/**
* Returns a an ExecutionResult with the {@code result} set, {@code completed} true and {@code success} true.
*/
public static <E extends Throwable> ExecutionResultWithException<E> successWithException(Object result) {
return new ExecutionResultWithException<>(result, null);
}

/**
* Returns a an ExecutionResult with the {@code failure} set, {@code completed} true and {@code success} false.
*/
public static <E extends Throwable> ExecutionResultWithException<E> failureWithException(E failure) {
return new ExecutionResultWithException<>(null, failure, false, 0, true, false, false);
}

@SuppressWarnings("unchecked")
public E getFailure() {
return (E) super.getFailure();
}

/**
* Returns a copy of the ExecutionResult with a non-result, and completed and success set to true. Returns
* {@code this} if {@link #success} and {@link #result} are unchanged.
*/
ExecutionResultWithException<E> withNonResult() {
return super.isSuccess() && super.getResult() == null && super.isNonResult() ?
this :
new ExecutionResultWithException<E>(null, null, true, super.getWaitNanos(), true, true, super.getSuccessAll());
}

/**
* Returns a copy of the ExecutionResult with the {@code result} value, and completed and success set to true. Returns
* {@code this} if {@link #success} and {@link #result} are unchanged.
*/
public ExecutionResultWithException<E> withResult(Object result) {
return super.isSuccess() && ((super.getResult() == null && result == null) || (super.getResult() != null && super.getResult().equals(result))) ?
this :
new ExecutionResultWithException<>(result, null, super.isNonResult(), super.getWaitNanos(), true, true, super.getSuccessAll());
}

/**
* Returns a copy of the ExecutionResult with the value set to true, else this if nothing has changed.
*/
@SuppressWarnings("unchecked")
public ExecutionResultWithException<E> withComplete() {
return super.isComplete() ? this : new ExecutionResultWithException<E>(super.getResult(), (E) super.getFailure(), super.isNonResult(), super.getWaitNanos(), true, super.isSuccess(), super.getSuccessAll());
}

/**
* Returns a copy of the ExecutionResult with the {@code completed} and {@code success} values.
*/
@SuppressWarnings("unchecked")
ExecutionResultWithException<E> with(boolean completed, boolean success) {
return super.isComplete() == completed && super.isSuccess() == success ?
this :
new ExecutionResultWithException<E>(super.getResult(), (E) super.getFailure(), super.isNonResult(), super.getWaitNanos(), completed, success,
super.isSuccess() && super.getSuccessAll());
}

/**
* Returns a copy of the ExecutionResult with the {@code waitNanos}, {@code completed} and {@code success} values.
*/
@SuppressWarnings("unchecked")
public ExecutionResultWithException<E> with(long waitNanos, boolean completed, boolean success) {
return super.getWaitNanos() == waitNanos && super.isComplete() == completed && super.isSuccess() == success ?
this :
new ExecutionResultWithException<E>(super.getResult(), (E) super.getFailure(), super.isNonResult(), waitNanos, completed, success,
super.isSuccess() && super.getSuccessAll());
}

@Override
public String toString() {
return "ExecutionResult[" + "result=" + super.getResult() + ", failure=" + super.getFailure() + ", nonResult=" + super.isNonResult()
+ ", waitNanos=" + super.getWaitNanos() + ", complete=" + super.isComplete() + ", success=" + super.isSuccess() + ", successAll=" + super.getSuccessAll()
+ ']';
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ExecutionResultWithException<E> that = (ExecutionResultWithException<E>) o;
return Objects.equals(super.getResult(), that.getResult()) && Objects.equals(super.getFailure(), that.getFailure());
}

@Override
public int hashCode() {
return Objects.hash(super.getResult(), super.getFailure());
}
}
53 changes: 46 additions & 7 deletions src/main/java/net/jodah/failsafe/FailsafeExecutor.java
Expand Up @@ -15,18 +15,36 @@
*/
package net.jodah.failsafe;

import net.jodah.failsafe.event.ExecutionCompletedEvent;
import net.jodah.failsafe.function.*;
import net.jodah.failsafe.internal.EventListener;
import net.jodah.failsafe.internal.util.Assert;
import net.jodah.failsafe.util.concurrent.Scheduler;
import static net.jodah.failsafe.Functions.getPromise;
import static net.jodah.failsafe.Functions.getPromiseExecution;
import static net.jodah.failsafe.Functions.getPromiseOfStage;
import static net.jodah.failsafe.Functions.getPromiseOfStageExecution;
import static net.jodah.failsafe.Functions.toAsyncSupplier;
import static net.jodah.failsafe.Functions.toCtxSupplier;
import static net.jodah.failsafe.Functions.toSupplier;

import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Function;
import java.util.function.Supplier;

import static net.jodah.failsafe.Functions.*;
import net.jodah.failsafe.event.ExecutionCompletedEvent;
import net.jodah.failsafe.function.AsyncRunnable;
import net.jodah.failsafe.function.AsyncSupplier;
import net.jodah.failsafe.function.CheckedConsumer;
import net.jodah.failsafe.function.CheckedRunnable;
import net.jodah.failsafe.function.CheckedSupplier;
import net.jodah.failsafe.function.CheckedSupplierWithException;
import net.jodah.failsafe.function.ContextualRunnable;
import net.jodah.failsafe.function.ContextualSupplier;
import net.jodah.failsafe.internal.EventListener;
import net.jodah.failsafe.internal.util.Assert;
import net.jodah.failsafe.util.concurrent.Scheduler;

/**
* <p>
Expand Down Expand Up @@ -67,6 +85,10 @@ public <T extends R> T get(CheckedSupplier<T> supplier) {
return call(execution -> Assert.notNull(supplier, "supplier"));
}

public <T extends R, E extends Throwable> T getWithException(CheckedSupplierWithException<T, E> supplier) throws E {
return callWithException(execution -> Assert.notNull(supplier, "supplier"));
}

/**
* Executes the {@code supplier} until a successful result is returned or the configured policies are exceeded.
*
Expand Down Expand Up @@ -385,6 +407,23 @@ private <T> T call(Function<Execution, CheckedSupplier<?>> supplierFn) {
return (T) result.getResult();
}

@SuppressWarnings("unchecked")
private <T, E extends Throwable> T callWithException(Function<Execution, CheckedSupplierWithException<?, E>> supplierFn) throws E {
Execution execution = new Execution(this);
Supplier<ExecutionResultWithException<E>> supplier = Functions.getWithException(supplierFn.apply(execution), execution);

ExecutionResultWithException<E> result = execution.executeSyncWithException(supplier);
Throwable failure = result.getFailure();
if (failure != null) {
if (failure instanceof RuntimeException)
throw (RuntimeException) failure;
if (failure instanceof Error)
throw (Error) failure;
throw (E) failure;
}
return (T) result.getResult();
}

/**
* Calls the asynchronous {@code supplier} via the configured Scheduler, handling results according to the configured
* policies.
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/net/jodah/failsafe/Functions.java
Expand Up @@ -66,6 +66,35 @@ else if (throwable instanceof InterruptedException)
};
}

static <T, E extends Throwable> Supplier<ExecutionResultWithException<E>> getWithException(CheckedSupplierWithException<T, E> supplier, AbstractExecution execution) throws E {
return () -> {
ExecutionResultWithException<E> result;
Throwable throwable = null;
try {
execution.preExecute();
result = ExecutionResultWithException.successWithException(supplier.get());
} catch (Throwable t) {
throwable = t;
// this probably needs some extra code in case an unexpected Exception is thrown
result = ExecutionResultWithException.failureWithException((E) t);
} finally {
// Guard against race with Timeout interruption
synchronized (execution) {
execution.canInterrupt = false;
if (execution.interrupted)
// Clear interrupt flag if interruption was intended
Thread.interrupted();
else if (throwable instanceof InterruptedException)
// Set interrupt flag if interrupt occurred but was not intentional
Thread.currentThread().interrupt();
}
}

execution.record(result);
return result;
};
}

/**
* Returns a Supplier that pre-executes the {@code execution}, applies the {@code supplier}, records the result and
* returns a promise containing the result.
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/net/jodah/failsafe/PolicyExecutor.java
Expand Up @@ -45,6 +45,10 @@ protected ExecutionResult preExecute() {
return null;
}

protected <E extends Throwable>ExecutionResultWithException<E> preExecuteWithException() {
return null;
}

/**
* Performs an execution by calling pre-execute else calling the supplier and doing a post-execute.
*/
Expand All @@ -58,6 +62,16 @@ protected Supplier<ExecutionResult> supply(Supplier<ExecutionResult> supplier, S
};
}

protected <E extends Throwable> Supplier<ExecutionResultWithException<E>> supplyWithException(Supplier<ExecutionResultWithException<E>> supplier, Scheduler scheduler) {
return () -> {
ExecutionResultWithException<E> result = preExecuteWithException();
if (result != null)
return result;

return postExecuteWithException(supplier.get());
};
}

/**
* Performs synchronous post-execution handling for a {@code result}.
*/
Expand All @@ -74,6 +88,19 @@ protected ExecutionResult postExecute(ExecutionResult result) {
return result;
}

protected <E extends Throwable> ExecutionResultWithException<E> postExecuteWithException(ExecutionResultWithException<E> result) {
if (isFailure(result)) {
result = onFailureWithException(result.with(false, false));
callFailureListener(result);
} else {
result = result.with(true, true);
onSuccess(result);
callSuccessListener(result);
}

return result;
}

/**
* Performs an async execution by calling pre-execute else calling the supplier and doing a post-execute.
*/
Expand Down Expand Up @@ -134,6 +161,11 @@ protected ExecutionResult onFailure(ExecutionResult result) {
return result;
}

protected <E extends Throwable> ExecutionResultWithException<E> onFailureWithException(ExecutionResultWithException<E> result) {
return result;
}


/**
* Performs potentially asynchrononus post-execution handling for a failed {@code result}, possibly creating a new
* result, else returning the original {@code result}.
Expand Down
@@ -0,0 +1,6 @@
package net.jodah.failsafe.function;

@FunctionalInterface
public interface CheckedSupplierWithException<T, E extends Throwable> {
T get() throws E;
}
@@ -0,0 +1,35 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package net.jodah.failsafe.functional;

import java.io.IOException;

import org.testng.annotations.Test;

import net.jodah.failsafe.Failsafe;
import net.jodah.failsafe.RetryPolicy;

@Test
public class SupplierWithExceptionTest {
@Test(expectedExceptions = IOException.class)
public void testCheckedException() throws IOException {
RetryPolicy<Object> retryPolicy = new RetryPolicy<>();

Failsafe.with(retryPolicy).getWithException(() -> {
throw new IOException();
});
}
}

0 comments on commit 1dc0c8d

Please sign in to comment.