dev.failsafe.FailsafeExecutor Maven / Gradle / Ivy
/*
* 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 dev.failsafe;
import dev.failsafe.event.EventListener;
import dev.failsafe.event.ExecutionCompletedEvent;
import dev.failsafe.function.*;
import dev.failsafe.internal.EventHandler;
import dev.failsafe.internal.util.Assert;
import dev.failsafe.spi.AsyncExecutionInternal;
import dev.failsafe.spi.ExecutionResult;
import dev.failsafe.spi.FailsafeFuture;
import dev.failsafe.spi.Scheduler;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import static dev.failsafe.Functions.*;
/**
*
* An executor that handles failures according to configured {@link FailurePolicyBuilder policies}. Can be created via
* {@link Failsafe#with(Policy, Policy[])} to support policy based execution failure handling, or {@link
* Failsafe#none()} to support execution with no failure handling.
*
* Async executions are run by default on the {@link ForkJoinPool#commonPool()}. Alternative executors can be configured
* via {@link #with(ScheduledExecutorService)} and similar methods. All async executions are cancellable and
* interruptable via the returned CompletableFuture, even those run by a {@link ForkJoinPool} or {@link
* CompletionStage}.
*
* @param result type
* @author Jonathan Halterman
*/
public class FailsafeExecutor {
private Scheduler scheduler = Scheduler.DEFAULT;
private Executor executor;
/** Policies sorted outer-most first */
final List extends Policy> policies;
private EventHandler completeHandler;
private volatile EventHandler failureHandler;
private volatile EventHandler successHandler;
/**
* @throws IllegalArgumentException if {@code policies} is empty
*/
FailsafeExecutor(List extends Policy> policies) {
this.policies = policies;
}
/**
* Returns the currently configured policies.
*
* @see #compose(Policy)
*/
public List extends Policy> getPolicies() {
return policies;
}
/**
* Returns a new {@code FailsafeExecutor} that composes the currently configured policies around the given {@code
* innerPolicy}. For example, consider:
*
*
* Failsafe.with(fallback).compose(retryPolicy).compose(circuitBreaker);
*
*
* This results in the following internal composition when executing a {@code runnable} or {@code supplier} and
* handling its result:
*
*
* Fallback(RetryPolicy(CircuitBreaker(Supplier)))
*
*
* This means the {@code CircuitBreaker} is first to evaluate the {@code Supplier}'s result, then the {@code
* RetryPolicy}, then the {@code Fallback}. Each policy makes its own determination as to whether the result
* represents a failure. This allows different policies to be used for handling different types of failures.
*
* @throws NullPointerException if {@code innerPolicy} is null
* @see #getPolicies()
*/
public > FailsafeExecutor compose(P innerPolicy) {
Assert.notNull(innerPolicy, "innerPolicy");
List> composed = new ArrayList<>(policies);
composed.add(innerPolicy);
return new FailsafeExecutor<>(composed);
}
/**
* Executes the {@code supplier} until a successful result is returned or the configured policies are exceeded.
*
* @throws NullPointerException if the {@code supplier} is null
* @throws FailsafeException if the {@code supplier} fails with a checked Exception. {@link
* FailsafeException#getCause()} can be used to learn the checked exception that caused the failure.
* @throws TimeoutExceededException if the execution fails because a {@link Timeout} is exceeded.
* @throws CircuitBreakerOpenException if the execution fails because a {@link CircuitBreaker} is open.
* @throws RateLimitExceededException if the execution fails because a {@link RateLimiter} is exceeded.
*/
public T get(CheckedSupplier supplier) {
return call(toCtxSupplier(supplier));
}
/**
* Executes the {@code supplier} until a successful result is returned or the configured policies are exceeded.
*
* @throws NullPointerException if the {@code supplier} is null
* @throws FailsafeException if the {@code supplier} fails with a checked Exception. {@link
* FailsafeException#getCause()} can be used to learn the checked exception that caused the failure.
* @throws TimeoutExceededException if the execution fails because a {@link Timeout} is exceeded.
* @throws CircuitBreakerOpenException if the execution fails because a {@link CircuitBreaker} is open.
* @throws RateLimitExceededException if the execution fails because a {@link RateLimiter} is exceeded.
*/
public T get(ContextualSupplier supplier) {
return call(Assert.notNull(supplier, "supplier"));
}
/**
* Executes the {@code supplier} asynchronously until a successful result is returned or the configured policies are
* exceeded.
*
* - If the execution fails because a {@link Timeout} is exceeded, the resulting future is completed exceptionally
* with {@link TimeoutExceededException}.
* - If the execution fails because a {@link CircuitBreaker} is open, the resulting future is completed
* exceptionally with {@link CircuitBreakerOpenException}.
* - If the execution fails because a {@link RateLimiter} is exceeded, the resulting future is completed
* exceptionally with {@link RateLimitExceededException}.
*
*
* @throws NullPointerException if the {@code supplier} is null
* @throws RejectedExecutionException if the {@code supplier} cannot be scheduled for execution
*/
public CompletableFuture getAsync(CheckedSupplier supplier) {
return callAsync(future -> getPromise(toCtxSupplier(supplier), executor), false);
}
/**
* Executes the {@code supplier} asynchronously until a successful result is returned or the configured policies are
* exceeded.
*
* - If the execution fails because a {@link Timeout} is exceeded, the resulting future is completed exceptionally
* with {@link TimeoutExceededException}.
* - If the execution fails because a {@link CircuitBreaker} is open, the resulting future is completed
* exceptionally with {@link CircuitBreakerOpenException}.
* - If the execution fails because a {@link RateLimiter} is exceeded, the resulting future is completed
* exceptionally with {@link RateLimitExceededException}.
*
*
* @throws NullPointerException if the {@code supplier} is null
* @throws RejectedExecutionException if the {@code supplier} cannot be scheduled for execution
*/
public CompletableFuture getAsync(ContextualSupplier supplier) {
return callAsync(future -> getPromise(supplier, executor), false);
}
/**
* This method is intended for integration with asynchronous code.
*
* Executes the {@code runnable} asynchronously until a successful result is recorded or the configured policies are
* exceeded. Executions must be recorded via one of the {@code AsyncExecution.record} methods which will trigger
* failure handling, if needed, by the configured policies, else the resulting {@link CompletableFuture} will be
* completed. Any exception that is thrown from the {@code runnable} will automatically be recorded via {@code
* AsyncExecution.recordFailure}.
*
*
* - If the execution fails because a {@link Timeout} is exceeded, the resulting future is completed exceptionally
* with {@link TimeoutExceededException}.
* - If the execution fails because a {@link CircuitBreaker} is open, the resulting future is completed
* exceptionally with {@link CircuitBreakerOpenException}.
* - If the execution fails because a {@link RateLimiter} is exceeded, the resulting future is completed
* exceptionally with {@link RateLimitExceededException}.
*
*
* @throws NullPointerException if the {@code supplier} is null
* @throws RejectedExecutionException if the {@code supplier} cannot be scheduled for execution
*/
public CompletableFuture getAsyncExecution(AsyncRunnable runnable) {
return callAsync(future -> getPromiseExecution(runnable, executor), true);
}
/**
* Executes the {@code supplier} asynchronously until the resulting future is successfully completed or the configured
* policies are exceeded.
* Cancelling the resulting {@link CompletableFuture} will automatically cancels the supplied {@link
* CompletionStage} if it's a {@link Future}.
*
* - If the execution fails because a {@link Timeout} is exceeded, the resulting future is completed exceptionally
* with {@link TimeoutExceededException}.
* - If the execution fails because a {@link CircuitBreaker} is open, the resulting future is completed
* exceptionally with {@link CircuitBreakerOpenException}.
* - If the execution fails because a {@link RateLimiter} is exceeded, the resulting future is completed
* exceptionally with {@link RateLimitExceededException}.
*
*
* @throws NullPointerException if the {@code supplier} is null
* @throws RejectedExecutionException if the {@code supplier} cannot be scheduled for execution
*/
public CompletableFuture getStageAsync(CheckedSupplier extends CompletionStage> supplier) {
return callAsync(future -> getPromiseOfStage(toCtxSupplier(supplier), future), false);
}
/**
* Executes the {@code supplier} asynchronously until the resulting future is successfully completed or the configured
* policies are exceeded.
* Cancelling the resulting {@link CompletableFuture} will automatically cancels the supplied {@link
* CompletionStage} if it's a {@link Future}.
*
* - If the execution fails because a {@link Timeout} is exceeded, the resulting future is completed exceptionally
* with {@link TimeoutExceededException}.
* - If the execution fails because a {@link CircuitBreaker} is open, the resulting future is completed
* exceptionally with {@link CircuitBreakerOpenException}.
* - If the execution fails because a {@link RateLimiter} is exceeded, the resulting future is completed
* exceptionally with {@link RateLimitExceededException}.
*
*
* @throws NullPointerException if the {@code supplier} is null
* @throws RejectedExecutionException if the {@code supplier} cannot be scheduled for execution
*/
public CompletableFuture getStageAsync(
ContextualSupplier> supplier) {
return callAsync(future -> getPromiseOfStage(supplier, future), false);
}
/**
* Executes the {@code runnable} until successful or until the configured policies are exceeded.
*
* @throws NullPointerException if the {@code runnable} is null
* @throws FailsafeException if the {@code runnable} fails with a checked Exception. {@link
* FailsafeException#getCause()} can be used to learn the checked exception that caused the failure.
* @throws TimeoutExceededException if the execution fails because a {@link Timeout} is exceeded.
* @throws CircuitBreakerOpenException if the execution fails because a {@link CircuitBreaker} is open.
* @throws RateLimitExceededException if the execution fails because a {@link RateLimiter} is exceeded.
*/
public void run(CheckedRunnable runnable) {
call(toCtxSupplier(runnable));
}
/**
* Executes the {@code runnable} until successful or until the configured policies are exceeded.
*
* @throws NullPointerException if the {@code runnable} is null
* @throws FailsafeException if the {@code runnable} fails with a checked Exception. {@link
* FailsafeException#getCause()} can be used to learn the checked exception that caused the failure.
* @throws TimeoutExceededException if the execution fails because a {@link Timeout} is exceeded.
* @throws CircuitBreakerOpenException if the execution fails because a {@link CircuitBreaker} is open.
* @throws RateLimitExceededException if the execution fails because a {@link RateLimiter} is exceeded.
*/
public void run(ContextualRunnable runnable) {
call(toCtxSupplier(runnable));
}
/**
* Executes the {@code runnable} asynchronously until successful or until the configured policies are exceeded.
*
* - If the execution fails because a {@link Timeout} is exceeded, the resulting future is completed exceptionally
* with {@link TimeoutExceededException}.
* - If the execution fails because a {@link CircuitBreaker} is open, the resulting future is completed
* exceptionally with {@link CircuitBreakerOpenException}.
* - If the execution fails because a {@link RateLimiter} is exceeded, the resulting future is completed
* exceptionally with {@link RateLimitExceededException}.
*
*
* @throws NullPointerException if the {@code runnable} is null
* @throws RejectedExecutionException if the {@code runnable} cannot be scheduled for execution
*/
public CompletableFuture runAsync(CheckedRunnable runnable) {
return callAsync(future -> getPromise(toCtxSupplier(runnable), executor), false);
}
/**
* Executes the {@code runnable} asynchronously until successful or until the configured policies are exceeded.
*
* - If the execution fails because a {@link Timeout} is exceeded, the resulting future is completed exceptionally
* with {@link TimeoutExceededException}.
* - If the execution fails because a {@link CircuitBreaker} is open, the resulting future is completed
* exceptionally with {@link CircuitBreakerOpenException}.
* - If the execution fails because a {@link RateLimiter} is exceeded, the resulting future is completed
* exceptionally with {@link RateLimitExceededException}.
*
*
* @throws NullPointerException if the {@code runnable} is null
* @throws RejectedExecutionException if the {@code runnable} cannot be scheduled for execution
*/
public CompletableFuture runAsync(ContextualRunnable runnable) {
return callAsync(future -> getPromise(toCtxSupplier(runnable), executor), false);
}
/**
* This method is intended for integration with asynchronous code.
*
* Executes the {@code runnable} asynchronously until a successful result is recorded or the configured policies are
* exceeded. Executions must be recorded via one of the {@code AsyncExecution.record} methods which will trigger
* failure handling, if needed, by the configured policies, else the resulting {@link CompletableFuture} will be
* completed. Any exception that is thrown from the {@code runnable} will automatically be recorded via {@code
* AsyncExecution.recordFailure}.
*
*
* - If the execution fails because a {@link Timeout} is exceeded, the resulting future is completed exceptionally
* with {@link TimeoutExceededException}.
* - If the execution fails because a {@link CircuitBreaker} is open, the resulting future is completed
* exceptionally with {@link CircuitBreakerOpenException}.
* - If the execution fails because a {@link RateLimiter} is exceeded, the resulting future is completed
* exceptionally with {@link RateLimitExceededException}.
*
*
* @throws NullPointerException if the {@code runnable} is null
* @throws RejectedExecutionException if the {@code runnable} cannot be scheduled for execution
*/
public CompletableFuture runAsyncExecution(AsyncRunnable runnable) {
return callAsync(future -> getPromiseExecution(runnable, executor), true);
}
/**
* Registers the {@code listener} to be called when an execution is complete. This occurs when an execution is
* successful according to all policies, or all policies have been exceeded.
* Note: Any exceptions that are thrown from within the {@code listener} are ignored.
*/
public FailsafeExecutor onComplete(EventListener> listener) {
completeHandler = EventHandler.ofExecutionCompleted(Assert.notNull(listener, "listener"));
return this;
}
/**
* Registers the {@code listener} to be called when an execution fails. This occurs when the execution fails according
* to some policy, and all policies have been exceeded.
* Note: Any exceptions that are thrown from within the {@code listener} are ignored. To provide an alternative
* result for a failed execution, use a {@link Fallback}.
*/
public FailsafeExecutor onFailure(EventListener> listener) {
failureHandler = EventHandler.ofExecutionCompleted(Assert.notNull(listener, "listener"));
return this;
}
/**
* Registers the {@code listener} to be called when an execution is successful. If multiple policies, are configured,
* this handler is called when execution is complete and all policies succeed. If all policies do not
* succeed, then the {@link #onFailure(EventListener)} registered listener is called instead.
* Note: Any exceptions that are thrown from within the {@code listener} are ignored.
*/
public FailsafeExecutor onSuccess(EventListener> listener) {
successHandler = EventHandler.ofExecutionCompleted(Assert.notNull(listener, "listener"));
return this;
}
/**
* Configures the {@code scheduledExecutorService} to use for performing asynchronous executions and listener
* callbacks.
*
* Note: The {@code scheduledExecutorService} should have a core pool size of at least 2 in order for {@link Timeout
* timeouts} to work.
*
*
* @throws NullPointerException if {@code scheduledExecutorService} is null
* @throws IllegalArgumentException if the {@code scheduledExecutorService} has a core pool size of less than 2
*/
public FailsafeExecutor with(ScheduledExecutorService scheduledExecutorService) {
this.scheduler = Scheduler.of(Assert.notNull(scheduledExecutorService, "scheduledExecutorService"));
return this;
}
/**
* Configures the {@code executorService} to use for performing asynchronous executions and listener callbacks. For
* async executions that require a delay, an internal ScheduledExecutorService will be used for the delay, then the
* {@code executorService} will be used for actual execution.
*
* Note: The {@code executorService} should have a core pool size or parallelism of at least 2 in order for {@link
* Timeout timeouts} to work.
*
*
* @throws NullPointerException if {@code executorService} is null
*/
public FailsafeExecutor with(ExecutorService executorService) {
this.scheduler = Scheduler.of(Assert.notNull(executorService, "executorService"));
return this;
}
/**
* Configures the {@code executor} to use as a wrapper around executions. If the {@code executor} is actually an
* instance of {@link ExecutorService}, then the {@code executor} will be configured via {@link
* #with(ExecutorService)} instead.
*
* The {@code executor} is responsible for propagating executions. Executions that normally return a result, such as
* {@link #get(CheckedSupplier)} will return {@code null} since the {@link Executor} interface does not support
* results.
*
* The {@code executor} will not be used for {@link #getStageAsync(CheckedSupplier) getStageAsync} calls since
* those require a returned result.
*
*
* @throws NullPointerException if {@code executor} is null
*/
public FailsafeExecutor with(Executor executor) {
Assert.notNull(executor, "executor");
if (executor instanceof ExecutorService)
with((ExecutorService) executor);
else
this.executor = executor;
return this;
}
/**
* Configures the {@code scheduler} to use for performing asynchronous executions and listener callbacks.
*
* @throws NullPointerException if {@code scheduler} is null
*/
public FailsafeExecutor with(Scheduler scheduler) {
this.scheduler = Assert.notNull(scheduler, "scheduler");
return this;
}
/**
* Calls the {@code innerSupplier} synchronously, handling results according to the configured policies.
*
* @throws FailsafeException if the {@code innerSupplier} fails with a checked Exception or if interrupted while
* waiting to perform a retry.
* @throws TimeoutExceededException if the execution fails because a {@link Timeout} is exceeded.
* @throws CircuitBreakerOpenException if the execution fails because a {@link CircuitBreaker} is open.
* @throws RateLimitExceededException if the execution fails because a {@link RateLimiter} is exceeded.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private T call(ContextualSupplier innerSupplier) {
SyncExecutionImpl execution = new SyncExecutionImpl(this, scheduler, Functions.get(innerSupplier, executor));
ExecutionResult result = execution.executeSync();
Throwable failure = result.getFailure();
if (failure != null) {
if (failure instanceof RuntimeException)
throw (RuntimeException) failure;
if (failure instanceof Error)
throw (Error) failure;
throw new FailsafeException(failure);
}
return result.getResult();
}
/**
* Calls the asynchronous {@code innerFn} via the configured Scheduler, handling results according to the configured
* policies.
*
* - If the execution fails because a {@link Timeout} is exceeded, the resulting future is completed exceptionally
* with {@link TimeoutExceededException}.
* - If the execution fails because a {@link CircuitBreaker} is open, the resulting future is completed
* exceptionally with {@link CircuitBreakerOpenException}.
* - If the execution fails because a {@link RateLimiter} is exceeded, the resulting future is completed
* exceptionally with {@link RateLimitExceededException}.
*
*
* @param asyncExecution whether this is a detached, async execution that must be manually completed
* @throws NullPointerException if the {@code innerFn} is null
* @throws RejectedExecutionException if the {@code innerFn} cannot be scheduled for execution
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private CompletableFuture callAsync(
Function, Function, CompletableFuture>>> innerFn,
boolean asyncExecution) {
FailsafeFuture future = new FailsafeFuture(completionHandler);
AsyncExecutionImpl execution = new AsyncExecutionImpl(policies, scheduler, future, asyncExecution,
innerFn.apply(future));
future.setExecution(execution);
execution.executeAsync();
return future;
}
final BiConsumer, ExecutionContext> completionHandler = (result, context) -> {
if (successHandler != null && result.getSuccessAll())
successHandler.handle(result, context);
else if (failureHandler != null && !result.getSuccessAll())
failureHandler.handle(result, context);
if (completeHandler != null)
completeHandler.handle(result, context);
};
}