Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add non-wrapped exceptions to get() #236

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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();
});
}
}