001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.util;
019
020import static java.util.stream.Collectors.toList;
021
022import java.io.IOException;
023import java.io.InterruptedIOException;
024import java.util.List;
025import java.util.concurrent.CompletableFuture;
026import java.util.concurrent.CompletionException;
027import java.util.concurrent.ExecutionException;
028import java.util.concurrent.Executor;
029import java.util.concurrent.Future;
030import java.util.concurrent.TimeUnit;
031import java.util.concurrent.TimeoutException;
032import java.util.function.BiConsumer;
033import org.apache.hadoop.hbase.exceptions.TimeoutIOException;
034import org.apache.yetus.audience.InterfaceAudience;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038/**
039 * Helper class for processing futures.
040 */
041@InterfaceAudience.Private
042public final class FutureUtils {
043
044  private static final Logger LOG = LoggerFactory.getLogger(FutureUtils.class);
045
046  private FutureUtils() {
047  }
048
049  /**
050   * This is method is used when you just want to add a listener to the given future. We will call
051   * {@link CompletableFuture#whenComplete(BiConsumer)} to register the {@code action} to the
052   * {@code future}. Ignoring the return value of a Future is considered as a bad practice as it may
053   * suppress exceptions thrown from the code that completes the future, and this method will catch
054   * all the exception thrown from the {@code action} to catch possible code bugs.
055   * <p/>
056   * And the error phone check will always report FutureReturnValueIgnored because every method in
057   * the {@link CompletableFuture} class will return a new {@link CompletableFuture}, so you always
058   * have one future that has not been checked. So we introduce this method and add a suppress
059   * warnings annotation here.
060   */
061  @SuppressWarnings("FutureReturnValueIgnored")
062  public static <T> void addListener(CompletableFuture<T> future,
063    BiConsumer<? super T, ? super Throwable> action) {
064    future.whenComplete((resp, error) -> {
065      try {
066        // See this post on stack overflow(shorten since the url is too long),
067        // https://s.apache.org/completionexception
068        // For a chain of CompleableFuture, only the first child CompletableFuture can get the
069        // original exception, others will get a CompletionException, which wraps the original
070        // exception. So here we unwrap it before passing it to the callback action.
071        action.accept(resp, unwrapCompletionException(error));
072      } catch (Throwable t) {
073        LOG.error("Unexpected error caught when processing CompletableFuture", t);
074      }
075    });
076  }
077
078  /**
079   * Almost the same with {@link #addListener(CompletableFuture, BiConsumer)} method above, the only
080   * exception is that we will call
081   * {@link CompletableFuture#whenCompleteAsync(BiConsumer, Executor)}.
082   * @see #addListener(CompletableFuture, BiConsumer)
083   */
084  @SuppressWarnings("FutureReturnValueIgnored")
085  public static <T> void addListener(CompletableFuture<T> future,
086    BiConsumer<? super T, ? super Throwable> action, Executor executor) {
087    future.whenCompleteAsync((resp, error) -> {
088      try {
089        action.accept(resp, unwrapCompletionException(error));
090      } catch (Throwable t) {
091        LOG.error("Unexpected error caught when processing CompletableFuture", t);
092      }
093    }, executor);
094  }
095
096  /**
097   * Log the error if the future indicates any failure.
098   */
099  public static void consume(CompletableFuture<?> future) {
100    addListener(future, (r, e) -> {
101      if (e != null) {
102        LOG.warn("Async operation fails", e);
103      }
104    });
105  }
106
107  /**
108   * Return a {@link CompletableFuture} which is same with the given {@code future}, but execute all
109   * the callbacks in the given {@code executor}.
110   */
111  public static <T> CompletableFuture<T> wrapFuture(CompletableFuture<T> future,
112    Executor executor) {
113    CompletableFuture<T> wrappedFuture = new CompletableFuture<>();
114    addListener(future, (r, e) -> {
115      if (e != null) {
116        wrappedFuture.completeExceptionally(e);
117      } else {
118        wrappedFuture.complete(r);
119      }
120    }, executor);
121    return wrappedFuture;
122  }
123
124  /**
125   * Get the cause of the {@link Throwable} if it is a {@link CompletionException}.
126   */
127  public static Throwable unwrapCompletionException(Throwable error) {
128    if (error instanceof CompletionException) {
129      Throwable cause = error.getCause();
130      if (cause != null) {
131        return cause;
132      }
133    }
134    return error;
135  }
136
137  // This method is used to record the stack trace that calling the FutureUtils.get method. As in
138  // async client, the retry will be done in the retry timer thread, so the exception we get from
139  // the CompletableFuture will have a stack trace starting from the root of the retry timer. If we
140  // just throw this exception out when calling future.get(by unwrapping the ExecutionException),
141  // the upper layer even can not know where is the exception thrown...
142  // See HBASE-22316.
143  private static void setStackTrace(Throwable error) {
144    StackTraceElement[] localStackTrace = Thread.currentThread().getStackTrace();
145    StackTraceElement[] originalStackTrace = error.getStackTrace();
146    StackTraceElement[] newStackTrace =
147      new StackTraceElement[localStackTrace.length + originalStackTrace.length + 1];
148    System.arraycopy(localStackTrace, 0, newStackTrace, 0, localStackTrace.length);
149    newStackTrace[localStackTrace.length] =
150      new StackTraceElement("--------Future", "get--------", null, -1);
151    System.arraycopy(originalStackTrace, 0, newStackTrace, localStackTrace.length + 1,
152      originalStackTrace.length);
153    error.setStackTrace(newStackTrace);
154  }
155
156  /**
157   * If we could propagate the given {@code error} directly, we will fill the stack trace with the
158   * current thread's stack trace so it is easier to trace where is the exception thrown. If not, we
159   * will just create a new IOException and then throw it.
160   */
161  public static IOException rethrow(Throwable error) throws IOException {
162    if (error instanceof IOException) {
163      setStackTrace(error);
164      throw (IOException) error;
165    } else if (error instanceof RuntimeException) {
166      setStackTrace(error);
167      throw (RuntimeException) error;
168    } else if (error instanceof Error) {
169      setStackTrace(error);
170      throw (Error) error;
171    } else {
172      throw new IOException(error);
173    }
174  }
175
176  /**
177   * A helper class for getting the result of a Future, and convert the error to an
178   * {@link IOException}.
179   */
180  public static <T> T get(Future<T> future) throws IOException {
181    try {
182      return future.get();
183    } catch (InterruptedException e) {
184      throw (IOException) new InterruptedIOException().initCause(e);
185    } catch (ExecutionException e) {
186      throw rethrow(e.getCause());
187    }
188  }
189
190  /**
191   * A helper class for getting the result of a Future with timeout, and convert the error to an
192   * {@link IOException}.
193   */
194  public static <T> T get(Future<T> future, long timeout, TimeUnit unit) throws IOException {
195    try {
196      return future.get(timeout, unit);
197    } catch (InterruptedException e) {
198      throw (IOException) new InterruptedIOException().initCause(e);
199    } catch (ExecutionException e) {
200      throw rethrow(e.getCause());
201    } catch (TimeoutException e) {
202      throw new TimeoutIOException(e);
203    }
204  }
205
206  /**
207   * Returns a CompletableFuture that is already completed exceptionally with the given exception.
208   */
209  public static <T> CompletableFuture<T> failedFuture(Throwable e) {
210    CompletableFuture<T> future = new CompletableFuture<>();
211    future.completeExceptionally(e);
212    return future;
213  }
214
215  /**
216   * Returns a new CompletableFuture that is completed when all of the given CompletableFutures
217   * complete. If any of the given CompletableFutures complete exceptionally, then the returned
218   * CompletableFuture also does so, with a CompletionException holding this exception as its cause.
219   * Otherwise, the results of all given CompletableFutures could be obtained by the new returned
220   * CompletableFuture.
221   */
222  public static <T> CompletableFuture<List<T>> allOf(List<CompletableFuture<T>> futures) {
223    return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
224      .thenApply(v -> futures.stream().map(f -> f.getNow(null)).collect(toList()));
225  }
226}