| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * 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.android.internal.infra; |
| |
| import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; |
| |
| import android.annotation.CallSuper; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.os.RemoteException; |
| import android.util.ExceptionUtils; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.util.Preconditions; |
| import com.android.internal.util.function.pooled.PooledLambda; |
| |
| import java.util.concurrent.CancellationException; |
| import java.util.concurrent.CompletableFuture; |
| import java.util.concurrent.CompletionStage; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| import java.util.function.BiConsumer; |
| import java.util.function.BiFunction; |
| import java.util.function.Function; |
| import java.util.function.Supplier; |
| |
| /** |
| * A customized {@link CompletableFuture} with focus on reducing the number of allocations involved |
| * in a typical future usage scenario for Android. |
| * |
| * <p> |
| * In particular this involves allocations optimizations in: |
| * <ul> |
| * <li>{@link #thenCompose(Function)}</li> |
| * <li>{@link #thenApply(Function)}</li> |
| * <li>{@link #thenCombine(CompletionStage, BiFunction)}</li> |
| * <li>{@link #orTimeout(long, TimeUnit)}</li> |
| * <li>{@link #whenComplete(BiConsumer)}</li> |
| * </ul> |
| * As well as their *Async versions. |
| * |
| * <p> |
| * You can pass {@link AndroidFuture} across an IPC. |
| * When doing so, completing the future on the other side will propagate the completion back, |
| * effectively acting as an error-aware remote callback. |
| * |
| * <p> |
| * {@link AndroidFuture} is {@link Parcelable} iff its wrapped type {@code T} is |
| * effectively parcelable, i.e. is supported by {@link Parcel#readValue}/{@link Parcel#writeValue}. |
| * |
| * @param <T> see {@link CompletableFuture} |
| */ |
| public class AndroidFuture<T> extends CompletableFuture<T> implements Parcelable { |
| |
| private static final boolean DEBUG = false; |
| private static final String LOG_TAG = AndroidFuture.class.getSimpleName(); |
| |
| private final @NonNull Object mLock = new Object(); |
| @GuardedBy("mLock") |
| private @Nullable BiConsumer<? super T, ? super Throwable> mListener; |
| @GuardedBy("mLock") |
| private @Nullable Executor mListenerExecutor = DIRECT_EXECUTOR; |
| private @NonNull Handler mTimeoutHandler = Handler.getMain(); |
| private final @Nullable IAndroidFuture mRemoteOrigin; |
| |
| public AndroidFuture() { |
| super(); |
| mRemoteOrigin = null; |
| } |
| |
| AndroidFuture(Parcel in) { |
| super(); |
| if (in.readBoolean()) { |
| // Done |
| if (in.readBoolean()) { |
| // Failed |
| try { |
| in.readException(); |
| } catch (Throwable e) { |
| completeExceptionally(e); |
| } |
| if (!isCompletedExceptionally()) { |
| throw new IllegalStateException( |
| "Error unparceling AndroidFuture: exception expected"); |
| } |
| } else { |
| // Success |
| complete((T) in.readValue(null)); |
| } |
| mRemoteOrigin = null; |
| } else { |
| // Not done |
| mRemoteOrigin = IAndroidFuture.Stub.asInterface(in.readStrongBinder()); |
| } |
| } |
| |
| /** |
| * Create a completed future with the given value. |
| * |
| * @param value the value for the completed future |
| * @param <U> the type of the value |
| * @return the completed future |
| */ |
| @NonNull |
| public static <U> AndroidFuture<U> completedFuture(U value) { |
| AndroidFuture<U> future = new AndroidFuture<>(); |
| future.complete(value); |
| return future; |
| } |
| |
| @Override |
| public boolean complete(@Nullable T value) { |
| boolean changed = super.complete(value); |
| if (changed) { |
| onCompleted(value, null); |
| } |
| return changed; |
| } |
| |
| @Override |
| public boolean completeExceptionally(@NonNull Throwable ex) { |
| boolean changed = super.completeExceptionally(ex); |
| if (changed) { |
| onCompleted(null, ex); |
| } |
| return changed; |
| } |
| |
| @Override |
| public boolean cancel(boolean mayInterruptIfRunning) { |
| boolean changed = super.cancel(mayInterruptIfRunning); |
| if (changed) { |
| try { |
| get(); |
| throw new IllegalStateException("Expected CancellationException"); |
| } catch (CancellationException ex) { |
| onCompleted(null, ex); |
| } catch (Throwable e) { |
| throw new IllegalStateException("Expected CancellationException", e); |
| } |
| } |
| return changed; |
| } |
| |
| @CallSuper |
| protected void onCompleted(@Nullable T res, @Nullable Throwable err) { |
| cancelTimeout(); |
| |
| if (DEBUG) { |
| Log.i(LOG_TAG, this + " completed with result " + (err == null ? res : err), |
| new RuntimeException()); |
| } |
| |
| BiConsumer<? super T, ? super Throwable> listener; |
| synchronized (mLock) { |
| listener = mListener; |
| mListener = null; |
| } |
| |
| if (listener != null) { |
| callListenerAsync(listener, res, err); |
| } |
| |
| if (mRemoteOrigin != null) { |
| try { |
| mRemoteOrigin.complete(this /* resultContainer */); |
| } catch (RemoteException e) { |
| Log.e(LOG_TAG, "Failed to propagate completion", e); |
| } |
| } |
| } |
| |
| @Override |
| public AndroidFuture<T> whenComplete(@NonNull BiConsumer<? super T, ? super Throwable> action) { |
| return whenCompleteAsync(action, DIRECT_EXECUTOR); |
| } |
| |
| @Override |
| public AndroidFuture<T> whenCompleteAsync( |
| @NonNull BiConsumer<? super T, ? super Throwable> action, |
| @NonNull Executor executor) { |
| Preconditions.checkNotNull(action); |
| Preconditions.checkNotNull(executor); |
| synchronized (mLock) { |
| if (!isDone()) { |
| BiConsumer<? super T, ? super Throwable> oldListener = mListener; |
| |
| if (oldListener != null && executor != mListenerExecutor) { |
| // 2 listeners with different executors |
| // Too complex - give up on saving allocations and delegate to superclass |
| super.whenCompleteAsync(action, executor); |
| return this; |
| } |
| |
| mListenerExecutor = executor; |
| mListener = oldListener == null |
| ? action |
| : (res, err) -> { |
| callListener(oldListener, res, err); |
| callListener(action, res, err); |
| }; |
| return this; |
| } |
| } |
| |
| // isDone() == true at this point |
| T res = null; |
| Throwable err = null; |
| try { |
| res = get(); |
| } catch (ExecutionException e) { |
| err = e.getCause(); |
| } catch (Throwable e) { |
| err = e; |
| } |
| callListenerAsync(action, res, err); |
| return this; |
| } |
| |
| private void callListenerAsync(BiConsumer<? super T, ? super Throwable> listener, |
| @Nullable T res, @Nullable Throwable err) { |
| if (mListenerExecutor == DIRECT_EXECUTOR) { |
| callListener(listener, res, err); |
| } else { |
| mListenerExecutor.execute(PooledLambda |
| .obtainRunnable(AndroidFuture::callListener, listener, res, err) |
| .recycleOnUse()); |
| } |
| } |
| |
| /** |
| * Calls the provided listener, handling any exceptions that may arise. |
| */ |
| // package-private to avoid synthetic method when called from lambda |
| static <TT> void callListener( |
| @NonNull BiConsumer<? super TT, ? super Throwable> listener, |
| @Nullable TT res, @Nullable Throwable err) { |
| try { |
| try { |
| listener.accept(res, err); |
| } catch (Throwable t) { |
| if (err == null) { |
| // listener happy-case threw, but exception case might not throw, so report the |
| // same exception thrown by listener's happy-path to it again |
| listener.accept(null, t); |
| } else { |
| // listener exception-case threw |
| // give up on listener but preserve the original exception when throwing up |
| throw ExceptionUtils.appendCause(t, err); |
| } |
| } |
| } catch (Throwable t2) { |
| // give up on listener and log the result & exception to logcat |
| Log.e(LOG_TAG, "Failed to call whenComplete listener. res = " + res, t2); |
| } |
| } |
| |
| /** @inheritDoc */ |
| //@Override //TODO uncomment once java 9 APIs are exposed to frameworks |
| public AndroidFuture<T> orTimeout(long timeout, @NonNull TimeUnit unit) { |
| Message msg = PooledLambda.obtainMessage(AndroidFuture::triggerTimeout, this); |
| msg.obj = this; |
| mTimeoutHandler.sendMessageDelayed(msg, unit.toMillis(timeout)); |
| return this; |
| } |
| |
| void triggerTimeout() { |
| cancelTimeout(); |
| if (!isDone()) { |
| completeExceptionally(new TimeoutException()); |
| } |
| } |
| |
| /** |
| * Cancel all timeouts previously set with {@link #orTimeout}, if any. |
| * |
| * @return {@code this} for chaining |
| */ |
| public AndroidFuture<T> cancelTimeout() { |
| mTimeoutHandler.removeCallbacksAndMessages(this); |
| return this; |
| } |
| |
| /** |
| * Specifies the handler on which timeout is to be triggered |
| */ |
| public AndroidFuture<T> setTimeoutHandler(@NonNull Handler h) { |
| cancelTimeout(); |
| mTimeoutHandler = Preconditions.checkNotNull(h); |
| return this; |
| } |
| |
| @Override |
| public <U> AndroidFuture<U> thenCompose( |
| @NonNull Function<? super T, ? extends CompletionStage<U>> fn) { |
| return thenComposeAsync(fn, DIRECT_EXECUTOR); |
| } |
| |
| @Override |
| public <U> AndroidFuture<U> thenComposeAsync( |
| @NonNull Function<? super T, ? extends CompletionStage<U>> fn, |
| @NonNull Executor executor) { |
| return new ThenComposeAsync<>(this, fn, executor); |
| } |
| |
| private static class ThenComposeAsync<T, U> extends AndroidFuture<U> |
| implements BiConsumer<Object, Throwable>, Runnable { |
| private volatile T mSourceResult = null; |
| private final Executor mExecutor; |
| private volatile Function<? super T, ? extends CompletionStage<U>> mFn; |
| |
| ThenComposeAsync(@NonNull AndroidFuture<T> source, |
| @NonNull Function<? super T, ? extends CompletionStage<U>> fn, |
| @NonNull Executor executor) { |
| mFn = Preconditions.checkNotNull(fn); |
| mExecutor = Preconditions.checkNotNull(executor); |
| |
| // subscribe to first job completion |
| source.whenComplete(this); |
| } |
| |
| @Override |
| public void accept(Object res, Throwable err) { |
| if (err != null) { |
| // first or second job failed |
| completeExceptionally(err); |
| } else if (mFn != null) { |
| // first job completed |
| mSourceResult = (T) res; |
| // subscribe to second job completion asynchronously |
| mExecutor.execute(this); |
| } else { |
| // second job completed |
| complete((U) res); |
| } |
| } |
| |
| @Override |
| public void run() { |
| CompletionStage<U> secondJob; |
| try { |
| secondJob = Preconditions.checkNotNull(mFn.apply(mSourceResult)); |
| } catch (Throwable t) { |
| completeExceptionally(t); |
| return; |
| } finally { |
| // Marks first job complete |
| mFn = null; |
| } |
| // subscribe to second job completion |
| secondJob.whenComplete(this); |
| } |
| } |
| |
| @Override |
| public <U> AndroidFuture<U> thenApply(@NonNull Function<? super T, ? extends U> fn) { |
| return thenApplyAsync(fn, DIRECT_EXECUTOR); |
| } |
| |
| @Override |
| public <U> AndroidFuture<U> thenApplyAsync(@NonNull Function<? super T, ? extends U> fn, |
| @NonNull Executor executor) { |
| return new ThenApplyAsync<>(this, fn, executor); |
| } |
| |
| private static class ThenApplyAsync<T, U> extends AndroidFuture<U> |
| implements BiConsumer<T, Throwable>, Runnable { |
| private volatile T mSourceResult = null; |
| private final Executor mExecutor; |
| private final Function<? super T, ? extends U> mFn; |
| |
| ThenApplyAsync(@NonNull AndroidFuture<T> source, |
| @NonNull Function<? super T, ? extends U> fn, |
| @NonNull Executor executor) { |
| mExecutor = Preconditions.checkNotNull(executor); |
| mFn = Preconditions.checkNotNull(fn); |
| |
| // subscribe to job completion |
| source.whenComplete(this); |
| } |
| |
| @Override |
| public void accept(T res, Throwable err) { |
| if (err != null) { |
| completeExceptionally(err); |
| } else { |
| mSourceResult = res; |
| mExecutor.execute(this); |
| } |
| } |
| |
| @Override |
| public void run() { |
| try { |
| complete(mFn.apply(mSourceResult)); |
| } catch (Throwable t) { |
| completeExceptionally(t); |
| } |
| } |
| } |
| |
| @Override |
| public <U, V> AndroidFuture<V> thenCombine( |
| @NonNull CompletionStage<? extends U> other, |
| @NonNull BiFunction<? super T, ? super U, ? extends V> combineResults) { |
| return new ThenCombine<T, U, V>(this, other, combineResults); |
| } |
| |
| /** @see CompletionStage#thenCombine */ |
| public AndroidFuture<T> thenCombine(@NonNull CompletionStage<Void> other) { |
| return thenCombine(other, (res, aVoid) -> res); |
| } |
| |
| private static class ThenCombine<T, U, V> extends AndroidFuture<V> |
| implements BiConsumer<Object, Throwable> { |
| private volatile @Nullable T mResultT = null; |
| private volatile @NonNull CompletionStage<? extends U> mSourceU; |
| private final @NonNull BiFunction<? super T, ? super U, ? extends V> mCombineResults; |
| |
| ThenCombine(CompletableFuture<T> sourceT, |
| CompletionStage<? extends U> sourceU, |
| BiFunction<? super T, ? super U, ? extends V> combineResults) { |
| mSourceU = Preconditions.checkNotNull(sourceU); |
| mCombineResults = Preconditions.checkNotNull(combineResults); |
| |
| sourceT.whenComplete(this); |
| } |
| |
| @Override |
| public void accept(Object res, Throwable err) { |
| if (err != null) { |
| completeExceptionally(err); |
| return; |
| } |
| |
| if (mSourceU != null) { |
| // T done |
| mResultT = (T) res; |
| mSourceU.whenComplete(this); |
| } else { |
| // U done |
| try { |
| complete(mCombineResults.apply(mResultT, (U) res)); |
| } catch (Throwable t) { |
| completeExceptionally(t); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Similar to {@link CompletableFuture#supplyAsync} but |
| * runs the given action directly. |
| * |
| * The resulting future is immediately completed. |
| */ |
| public static <T> AndroidFuture<T> supply(Supplier<T> supplier) { |
| return supplyAsync(supplier, DIRECT_EXECUTOR); |
| } |
| |
| /** |
| * @see CompletableFuture#supplyAsync(Supplier, Executor) |
| */ |
| public static <T> AndroidFuture<T> supplyAsync(Supplier<T> supplier, Executor executor) { |
| return new SupplyAsync<>(supplier, executor); |
| } |
| |
| private static class SupplyAsync<T> extends AndroidFuture<T> implements Runnable { |
| private final @NonNull Supplier<T> mSupplier; |
| |
| SupplyAsync(Supplier<T> supplier, Executor executor) { |
| mSupplier = supplier; |
| executor.execute(this); |
| } |
| |
| @Override |
| public void run() { |
| try { |
| complete(mSupplier.get()); |
| } catch (Throwable t) { |
| completeExceptionally(t); |
| } |
| } |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| boolean done = isDone(); |
| dest.writeBoolean(done); |
| if (done) { |
| T result; |
| try { |
| result = get(); |
| } catch (Exception t) { |
| // Exceptions coming out of get() are wrapped in ExecutionException, which is not |
| // handled by Parcel. |
| if (t instanceof ExecutionException && t.getCause() instanceof Exception) { |
| t = (Exception) t.getCause(); |
| } |
| dest.writeBoolean(true); |
| dest.writeException(t); |
| return; |
| } |
| dest.writeBoolean(false); |
| dest.writeValue(result); |
| } else { |
| dest.writeStrongBinder(new IAndroidFuture.Stub() { |
| @Override |
| public void complete(AndroidFuture resultContainer) { |
| try { |
| AndroidFuture.this.complete((T) resultContainer.get()); |
| } catch (Throwable t) { |
| // If resultContainer was completed exceptionally, get() wraps the |
| // underlying exception in an ExecutionException. Unwrap it now to avoid |
| // double-layering ExecutionExceptions. |
| if (t instanceof ExecutionException && t.getCause() != null) { |
| t = t.getCause(); |
| } |
| completeExceptionally(t); |
| } |
| } |
| }.asBinder()); |
| } |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| public static final @NonNull Parcelable.Creator<AndroidFuture> CREATOR = |
| new Parcelable.Creator<AndroidFuture>() { |
| public AndroidFuture createFromParcel(Parcel parcel) { |
| return new AndroidFuture(parcel); |
| } |
| |
| public AndroidFuture[] newArray(int size) { |
| return new AndroidFuture[size]; |
| } |
| }; |
| } |