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

com.github.phantomthief.concurrent.MoreFutures Maven / Gradle / Ivy

There is a newer version: 0.1.55
Show newest version
package com.github.phantomthief.concurrent;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly;
import static java.lang.System.nanoTime;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;

import java.time.Duration;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.function.Supplier;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.phantomthief.util.ThrowableConsumer;
import com.github.phantomthief.util.ThrowableFunction;
import com.github.phantomthief.util.ThrowableRunnable;
import com.google.common.util.concurrent.AbstractFuture;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.ExecutionError;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.common.util.concurrent.UncheckedTimeoutException;

/**
 * @author w.vela
 * Created on 2018-06-25.
 */
public class MoreFutures {

    private static final Logger logger = LoggerFactory.getLogger(MoreFutures.class);

    /**
     * @throws UncheckedTimeoutException if timeout occurred.
     * @throws java.util.concurrent.CancellationException if task was canceled.
     * @throws ExecutionError if a {@link Error} occurred.
     * @throws UncheckedExecutionException if a normal Exception occurred.
     */
    public static  T getUnchecked(@Nonnull Future future,
            @Nonnull Duration duration) {
        checkNotNull(duration);
        return getUnchecked(future, duration.toNanos(), NANOSECONDS);
    }

    /**
     * @throws UncheckedTimeoutException if timeout occurred.
     * @throws java.util.concurrent.CancellationException if task was canceled.
     * @throws ExecutionError if a {@link Error} occurred.
     * @throws UncheckedExecutionException if a normal Exception occurred.
     */
    public static  T getUnchecked(@Nonnull Future future, @Nonnegative long timeout,
            @Nonnull TimeUnit unit) {
        return getUnchecked(future, timeout, unit, false);
    }

    /**
     * @throws UncheckedTimeoutException if timeout occurred.
     * @throws java.util.concurrent.CancellationException if task was canceled.
     * @throws ExecutionError if a {@link Error} occurred.
     * @throws UncheckedExecutionException if a normal Exception occurred.
     */
    public static  T getUnchecked(@Nonnull Future future, @Nonnegative long timeout,
            @Nonnull TimeUnit unit, boolean cancelOnTimeout) {
        checkArgument(timeout > 0);
        checkNotNull(future);
        try {
            return getUninterruptibly(future, timeout, unit);
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            if (cause instanceof Error) {
                throw new ExecutionError((Error) cause);
            } else {
                throw new UncheckedExecutionException(cause);
            }
        } catch (TimeoutException e) {
            if (cancelOnTimeout) {
                future.cancel(false);
            }
            throw new UncheckedTimeoutException(e);
        }
    }

    /**
     * @see #tryWait(Iterable, long, TimeUnit)
     *
     * @throws TryWaitFutureUncheckedException if not all calls are successful.
     */
    @Nonnull
    public static , V> Map tryWait(@Nonnull Iterable futures,
            @Nonnull Duration duration) throws TryWaitFutureUncheckedException {
        checkNotNull(futures);
        checkNotNull(duration);
        return tryWait(futures, duration.toNanos(), NANOSECONDS);
    }

    /**
     * A typical usage:
     * {@code 
     *  // a fail-safe example
     *  List> list = doSomeAsyncTasks();
     *  Map, User> success;
     *  try {
     *    success = tryWait(list, 1, SECONDS);
     *  } catch (TryWaitUncheckedException e) {
     *    success = e.getSuccess(); // there are still some success
     *  }
     *
     *  // a fail-fast example
     *  List> list = doSomeAsyncTasks();
     *  // don't try/catch the exception it throws.
     *  Map, User> success = tryWait(list, 1, SECONDS);
     * 
} * * @throws TryWaitUncheckedException if not all calls are successful. */ @Nonnull public static , V> Map tryWait(@Nonnull Iterable futures, @Nonnegative long timeout, @Nonnull TimeUnit unit) throws TryWaitFutureUncheckedException { checkNotNull(futures); checkArgument(timeout > 0); checkNotNull(unit); return tryWait(futures, timeout, unit, it -> it, TryWaitFutureUncheckedException::new); } /** * @see #tryWait(Iterable, long, TimeUnit, ThrowableFunction) * * @throws TryWaitUncheckedException if not all calls are successful. */ @Nonnull public static Map tryWait(@Nonnull Iterable keys, @Nonnull Duration duration, @Nonnull ThrowableFunction, X> asyncFunc) throws X, TryWaitUncheckedException { checkNotNull(keys); checkNotNull(duration); checkNotNull(asyncFunc); return tryWait(keys, duration.toNanos(), NANOSECONDS, asyncFunc); } /** * A typical usage: * {@code
     *  // a fail-safe example
     *  List list = getSomeIds();
     *  Map success;
     *  try {
     *    success = tryWait(list, 1, SECONDS, id -> executor.submit(() -> retrieve(id)));
     *  } catch (TryWaitUncheckedException e) {
     *    success = e.getSuccess(); // there are still some success
     *  }
     *
     *  // a fail-fast example
     *  List list = getSomeIds();
     *  // don't try/catch the exception it throws.
     *  Map success = tryWait(list, 1, SECONDS, id -> executor.submit(() -> retrieve(id)));
     * 
} * * @throws TryWaitUncheckedException if not all calls are successful. */ @Nonnull public static Map tryWait(@Nonnull Iterable keys, @Nonnegative long timeout, @Nonnull TimeUnit unit, @Nonnull ThrowableFunction, X> asyncFunc) throws X, TryWaitUncheckedException { return tryWait(keys, timeout, unit, asyncFunc, TryWaitUncheckedException::new); } @Nonnull private static Map tryWait(@Nonnull Iterable keys, @Nonnegative long timeout, @Nonnull TimeUnit unit, @Nonnull ThrowableFunction, X> asyncFunc, @Nonnull Function throwing) throws X { checkNotNull(keys); checkArgument(timeout > 0); checkNotNull(unit); checkNotNull(asyncFunc); Map, V> successMap = new LinkedHashMap<>(); Map, Throwable> failMap = new LinkedHashMap<>(); Map, TimeoutException> timeoutMap = new LinkedHashMap<>(); Map, CancellationException> cancelMap = new LinkedHashMap<>(); long remainingNanos = unit.toNanos(timeout); long end = nanoTime() + remainingNanos; Map, K> futureKeyMap = new IdentityHashMap<>(); for (K key : keys) { checkNotNull(key); Future future = asyncFunc.apply(key); checkNotNull(future); futureKeyMap.put(future, key); if (remainingNanos <= 0) { waitAndCollect(successMap, failMap, timeoutMap, cancelMap, future, 1L); continue; } waitAndCollect(successMap, failMap, timeoutMap, cancelMap, future, remainingNanos); remainingNanos = end - nanoTime(); } TryWaitResult result = new TryWaitResult<>(successMap, failMap, timeoutMap, cancelMap, futureKeyMap); if (failMap.isEmpty() && timeoutMap.isEmpty() && cancelMap.isEmpty()) { return result.getSuccess(); } else { throw throwing.apply(result); } } private static void waitAndCollect(Map, T> successMap, Map, Throwable> failMap, Map, TimeoutException> timeoutMap, Map, CancellationException> cancelMap, Future future, long thisWait) { try { T t = getUninterruptibly(future, thisWait, NANOSECONDS); successMap.put(future, t); } catch (CancellationException e) { cancelMap.put(future, e); } catch (TimeoutException e) { timeoutMap.put(future, e); } catch (ExecutionException e) { failMap.put(future, e.getCause()); } catch (Throwable e) { failMap.put(future, e); } } /** * @param task any exception throwing would cancel the task. user should swallow exceptions by self. * @param executor all task would be stopped after executor has been marked shutting down. * @return a future that can cancel the task. */ public static Future scheduleWithDynamicDelay(@Nonnull ScheduledExecutorService executor, @Nullable Duration initDelay, @Nonnull Scheduled task) { checkNotNull(executor); checkNotNull(task); AtomicBoolean canceled = new AtomicBoolean(false); AbstractFuture future = new AbstractFuture() { @Override public boolean cancel(boolean mayInterruptIfRunning) { canceled.set(true); return super.cancel(mayInterruptIfRunning); } }; executor.schedule(new ScheduledTaskImpl(executor, task, canceled), initDelay == null ? 0 : initDelay.toMillis(), MILLISECONDS); return future; } /** * @param task any exception throwing would be ignore and logged, task would not cancelled. * @param executor all task would be stopped after executor has been marked shutting down. * @return a future that can cancel the task. */ public static Future scheduleWithDynamicDelay(@Nonnull ScheduledExecutorService executor, @Nonnull Supplier delay, @Nonnull ThrowableRunnable task) { checkNotNull(delay); return scheduleWithDynamicDelay(executor, delay.get(), () -> { try { task.run(); } catch (Throwable e) { logger.error("", e); } return delay.get(); }); } /** * 用于替换 {@link Futures#transform(ListenableFuture, com.google.common.base.Function, Executor)} *

* 主要提供两个额外的功能: * 1. API使用jdk8 * 2. 提供了 {@link TimeoutListenableFuture} 的支持(保持Listener不会丢) */ public static ListenableFuture transform(ListenableFuture input, Function function, Executor executor) { @SuppressWarnings("Guava") com.google.common.base.Function realFunc; if (function instanceof com.google.common.base.Function) { //noinspection unchecked realFunc = (com.google.common.base.Function) function; } else { realFunc = function::apply; } ListenableFuture result = Futures.transform(input, realFunc, executor); if (input instanceof TimeoutListenableFuture) { TimeoutListenableFuture newResult = new TimeoutListenableFuture<>(result); for (ThrowableConsumer timeoutListener : ((TimeoutListenableFuture) input) .getTimeoutListeners()) { newResult.addTimeoutListener(timeoutListener); } return newResult; } else { return result; } } public static ListenableFuture transformAsync(ListenableFuture input, AsyncFunction function, Executor executor) { ListenableFuture result = Futures.transformAsync(input, function, executor); if (input instanceof TimeoutListenableFuture) { TimeoutListenableFuture newResult = new TimeoutListenableFuture<>(result); for (ThrowableConsumer timeoutListener : ((TimeoutListenableFuture) input) .getTimeoutListeners()) { newResult.addTimeoutListener(timeoutListener); } return newResult; } else { return result; } } public interface Scheduled { /** * @return a delay for next run. {@code null} means stop. */ @Nullable Duration run(); } private static class ScheduledTaskImpl implements Runnable { private final ScheduledExecutorService executorService; private final Scheduled scheduled; private final AtomicBoolean canceled; private ScheduledTaskImpl(ScheduledExecutorService executorService, Scheduled scheduled, AtomicBoolean canceled) { this.executorService = executorService; this.scheduled = scheduled; this.canceled = canceled; } @Override public void run() { if (canceled.get()) { return; } try { Duration delay = scheduled.run(); if (!canceled.get() && delay != null) { executorService.schedule(this, delay.toMillis(), MILLISECONDS); } } catch (Throwable e) { logger.error("", e); } } } }