/*
* Copyright 2012-present Facebook, Inc.
*
* 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 com.facebook.buck.util.concurrent;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.SettableFuture;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import javax.annotation.Nonnull;
public class MoreFutures {
/** Utility class: do not instantiate. */
private MoreFutures() {}
/**
* Invoke multiple callables on the provided executor and wait for all to return successfully. An
* exception is thrown (immediately) if any callable fails.
*
* @param executorService Executor service.
* @param callables Callables to call.
* @return List of values from each invoked callable in order if all callables execute without
* throwing.
* @throws ExecutionException If any callable throws an exception, the first of such is wrapped in
* an ExecutionException and thrown. Access via {@link
* java.util.concurrent.ExecutionException#getCause()}}.
*/
public static <V> List<V> getAll(
ListeningExecutorService executorService, Iterable<Callable<V>> callables)
throws ExecutionException, InterruptedException {
// Invoke.
ImmutableList.Builder<ListenableFuture<V>> futures = ImmutableList.builder();
for (Callable<V> callable : callables) {
ListenableFuture<V> future = executorService.submit(callable);
futures.add(future);
}
// Wait for completion or interruption.
ListenableFuture<List<V>> future = Futures.allAsList(futures.build());
try {
return future.get();
} catch (InterruptedException e) {
future.cancel(true);
throw e;
}
}
/**
* Create a convenience method for checking whether a future completed successfully because this
* does not appear to be possible to do in a more direct way:
* https://groups.google.com/forum/?fromgroups=#!topic/guava-discuss/rofEhagKnOc.
*
* @return true if the specified future has been resolved without throwing an exception or being
* cancelled.
*/
public static boolean isSuccess(ListenableFuture<?> future) throws InterruptedException {
if (!future.isDone()) {
return false;
}
try {
// Try to get the future, but ignore (and preserve) the thread interrupted state.
// This should be fast because we know the future is already complete.
future.get();
return true;
} catch (ExecutionException e) {
// The computation threw an exception, so it did not complete successfully.
return false;
} catch (CancellationException e) {
// The computation was cancelled, so it did not complete successfully.
return false;
}
}
/**
* Returns the failure for a {@link ListenableFuture}.
*
* @param future Must have completed unsuccessfully.
*/
public static Throwable getFailure(ListenableFuture<?> future) throws InterruptedException {
Preconditions.checkArgument(future.isDone());
Preconditions.checkArgument(!isSuccess(future));
try {
future.get();
throw new IllegalStateException("get() should have thrown an exception");
} catch (ExecutionException e) {
return e.getCause();
} catch (CancellationException e) {
throw new IllegalStateException(e);
}
}
public static <V> ListenableFuture<Void> addListenableCallback(
ListenableFuture<V> future, final FutureCallback<? super V> callback, Executor executor) {
final SettableFuture<Void> waiter = SettableFuture.create();
Futures.addCallback(
future,
new FutureCallback<V>() {
@Override
public void onSuccess(V result) {
try {
callback.onSuccess(result);
} catch (Throwable thrown) {
waiter.setException(thrown);
} finally {
waiter.set(null);
}
}
@Override
public void onFailure(@Nonnull Throwable throwable) {
try {
callback.onFailure(throwable);
} catch (Throwable thrown) {
waiter.setException(thrown);
} finally {
waiter.set(null);
}
}
},
executor);
return waiter;
}
public static <V> ListenableFuture<Void> addListenableCallback(
ListenableFuture<V> future, final FutureCallback<? super V> callback) {
return addListenableCallback(future, callback, directExecutor());
}
/**
* @return a {@link ListenableFuture} which fails if either input future fails or returns the
* value contained in {@code to} if they both succeed.
*/
public static <F, T> ListenableFuture<T> chainExceptions(
ListenableFuture<F> from, final ListenableFuture<T> to) {
return chainExceptions(from, to, directExecutor());
}
/**
* @return a {@link ListenableFuture} which fails if either input future fails or returns the
* value contained in {@code to} if they both succeed.
*/
public static <F, T> ListenableFuture<T> chainExceptions(
ListenableFuture<F> from, final ListenableFuture<T> to, Executor executor) {
return Futures.transformAsync(from, result -> to, executor);
}
public static <X extends Throwable> void propagateCauseIfInstanceOf(Throwable e, Class<X> type) {
if (e.getCause() != null && type.isInstance(e)) {
Throwables.throwIfUnchecked(e.getCause());
throw new RuntimeException(e.getCause());
}
}
}