All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.failsafe.FailsafeExecutor Maven / Gradle / Ivy

There is a newer version: 3.3.2
Show newest version
/*
 * 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> policies; private EventHandler completeHandler; private volatile EventHandler failureHandler; private volatile EventHandler successHandler; /** * @throws IllegalArgumentException if {@code policies} is empty */ FailsafeExecutor(List> policies) { this.policies = policies; } /** * Returns the currently configured policies. * * @see #compose(Policy) */ public List> 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> 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); }; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy