/* * Copyright (C) 2021 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 android.car.util.concurrent; import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Handler; import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.util.Slog; import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; import java.lang.reflect.Constructor; 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; /** * code copied from {@code com.android.internal.infra.AndroidFuture} * * @param see {@link CompletableFuture} * * @hide */ public class AndroidFuture extends CompletableFuture implements Parcelable { private static final boolean DEBUG = false; private static final String LOG_TAG = AndroidFuture.class.getSimpleName(); private static final Executor DIRECT_EXECUTOR = Runnable::run; private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0]; private static @Nullable Handler sMainHandler; private final @NonNull Object mLock = new Object(); @GuardedBy("mLock") private @Nullable BiConsumer mListener; @GuardedBy("mLock") private @Nullable Executor mListenerExecutor = DIRECT_EXECUTOR; private @NonNull Handler mTimeoutHandler = getMainHandler(); private final @Nullable IAndroidFuture mRemoteOrigin; public AndroidFuture() { super(); mRemoteOrigin = null; } AndroidFuture(Parcel in) { super(); if (in.readBoolean()) { // Done if (in.readBoolean()) { // Failed completeExceptionally(readThrowable(in)); } else { // Success complete((T) in.readValue(null)); } mRemoteOrigin = null; } else { // Not done mRemoteOrigin = IAndroidFuture.Stub.asInterface(in.readStrongBinder()); } } @NonNull private static Handler getMainHandler() { // This isn't thread-safe but we are okay with it. if (sMainHandler == null) { sMainHandler = new Handler(Looper.getMainLooper()); } return sMainHandler; } /** * Create a completed future with the given value. * * @param value the value for the completed future * @param the type of the value * @return the completed future */ @NonNull public static AndroidFuture completedFuture(U value) { AndroidFuture 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) { Slog.i(LOG_TAG, this + " completed with result " + (err == null ? res : err), new RuntimeException()); } BiConsumer 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) { Slog.e(LOG_TAG, "Failed to propagate completion", e); } } } @Override public AndroidFuture whenComplete(@NonNull BiConsumer action) { return whenCompleteAsync(action, DIRECT_EXECUTOR); } @Override public AndroidFuture whenCompleteAsync( @NonNull BiConsumer action, @NonNull Executor executor) { Preconditions.checkNotNull(action); Preconditions.checkNotNull(executor); synchronized (mLock) { if (!isDone()) { BiConsumer 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 listener, @Nullable T res, @Nullable Throwable err) { synchronized (mLock) { if (mListenerExecutor == DIRECT_EXECUTOR) { callListener(listener, res, err); } else { mListenerExecutor.execute(() -> callListener(listener, res, err)); } } } /** * Calls the provided listener, handling any exceptions that may arise. */ // package-private to avoid synthetic method when called from lambda static void callListener( @NonNull BiConsumer 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 t.addSuppressed(err); throw t; } } } catch (Throwable t2) { // give up on listener and log the result & exception to logcat Slog.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 orTimeout(long timeout, @NonNull TimeUnit unit) { mTimeoutHandler.postDelayed(this::triggerTimeout, this, 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 cancelTimeout() { mTimeoutHandler.removeCallbacksAndMessages(this); return this; } /** * Specifies the handler on which timeout is to be triggered */ public AndroidFuture setTimeoutHandler(@NonNull Handler h) { cancelTimeout(); mTimeoutHandler = Preconditions.checkNotNull(h); return this; } @Override public AndroidFuture thenCompose( @NonNull Function> fn) { return thenComposeAsync(fn, DIRECT_EXECUTOR); } @Override public AndroidFuture thenComposeAsync( @NonNull Function> fn, @NonNull Executor executor) { return new ThenComposeAsync<>(this, fn, executor); } private static class ThenComposeAsync extends AndroidFuture implements BiConsumer, Runnable { private volatile T mSourceResult = null; private final Executor mExecutor; private volatile Function> mFn; ThenComposeAsync(@NonNull AndroidFuture source, @NonNull Function> 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 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 AndroidFuture thenApply(@NonNull Function fn) { return thenApplyAsync(fn, DIRECT_EXECUTOR); } @Override public AndroidFuture thenApplyAsync(@NonNull Function fn, @NonNull Executor executor) { return new ThenApplyAsync<>(this, fn, executor); } private static class ThenApplyAsync extends AndroidFuture implements BiConsumer, Runnable { private volatile T mSourceResult = null; private final Executor mExecutor; private final Function mFn; ThenApplyAsync(@NonNull AndroidFuture source, @NonNull Function 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 AndroidFuture thenCombine( @NonNull CompletionStage other, @NonNull BiFunction combineResults) { return new ThenCombine(this, other, combineResults); } /** @see CompletionStage#thenCombine */ public AndroidFuture thenCombine(@NonNull CompletionStage other) { return thenCombine(other, (res, aVoid) -> res); } private static class ThenCombine extends AndroidFuture implements BiConsumer { private volatile @Nullable T mResultT = null; private volatile @NonNull CompletionStage mSourceU; private final @NonNull BiFunction mCombineResults; ThenCombine(CompletableFuture sourceT, CompletionStage sourceU, BiFunction 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; // Subscribe to the second job completion. mSourceU.whenComplete((r, e) -> { // Mark the first job completion by setting mSourceU to null, so that next time // the execution flow goes to the else case below. mSourceU = null; accept(r, e); }); } 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 AndroidFuture supply(Supplier supplier) { return supplyAsync(supplier, DIRECT_EXECUTOR); } /** * @see CompletableFuture#supplyAsync(Supplier, Executor) */ public static AndroidFuture supplyAsync(Supplier supplier, Executor executor) { return new SupplyAsync<>(supplier, executor); } private static class SupplyAsync extends AndroidFuture implements Runnable { private final @NonNull Supplier mSupplier; SupplyAsync(Supplier 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 (Throwable t) { dest.writeBoolean(true); writeThrowable(dest, unwrapExecutionException(t)); return; } dest.writeBoolean(false); dest.writeValue(result); } else { dest.writeStrongBinder(new IAndroidFuture.Stub() { @Override public void complete(AndroidFuture resultContainer) { boolean changed; try { changed = AndroidFuture.this.complete((T) resultContainer.get()); } catch (Throwable t) { changed = completeExceptionally(unwrapExecutionException(t)); } if (!changed) { Slog.w(LOG_TAG, "Remote result " + resultContainer + " ignored, as local future is already completed: " + AndroidFuture.this); } } }.asBinder()); } } /** * Exceptions coming out of {@link #get} are wrapped in {@link ExecutionException} */ Throwable unwrapExecutionException(Throwable t) { return t instanceof ExecutionException ? t.getCause() : t; } /** * Alternative to {@link Parcel#writeException} that stores the stack trace, in a * way consistent with the binder IPC exception propagation behavior. */ private static void writeThrowable(@NonNull Parcel parcel, @Nullable Throwable throwable) { boolean hasThrowable = throwable != null; parcel.writeBoolean(hasThrowable); if (!hasThrowable) { return; } boolean isFrameworkParcelable = throwable instanceof Parcelable && throwable.getClass().getClassLoader() == Parcelable.class.getClassLoader(); parcel.writeBoolean(isFrameworkParcelable); if (isFrameworkParcelable) { parcel.writeParcelable((Parcelable) throwable, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); return; } parcel.writeString(throwable.getClass().getName()); parcel.writeString(throwable.getMessage()); StackTraceElement[] stackTrace = throwable.getStackTrace(); StringBuilder stackTraceBuilder = new StringBuilder(); int truncatedStackTraceLength = Math.min(stackTrace != null ? stackTrace.length : 0, 5); for (int i = 0; i < truncatedStackTraceLength; i++) { if (i > 0) { stackTraceBuilder.append('\n'); } stackTraceBuilder.append("\tat ").append(stackTrace[i]); } parcel.writeString(stackTraceBuilder.toString()); writeThrowable(parcel, throwable.getCause()); } /** * @see #writeThrowable */ private static @Nullable Throwable readThrowable(@NonNull Parcel parcel) { final boolean hasThrowable = parcel.readBoolean(); if (!hasThrowable) { return null; } boolean isFrameworkParcelable = parcel.readBoolean(); if (isFrameworkParcelable) { return parcel.readParcelable(Parcelable.class.getClassLoader()); } String className = parcel.readString(); String message = parcel.readString(); String stackTrace = parcel.readString(); String messageWithStackTrace = message + '\n' + stackTrace; Throwable throwable; try { Class clazz = Class.forName(className, true, Parcelable.class.getClassLoader()); if (Throwable.class.isAssignableFrom(clazz)) { Constructor constructor = clazz.getConstructor(String.class); throwable = (Throwable) constructor.newInstance(messageWithStackTrace); } else { android.util.EventLog.writeEvent(0x534e4554, "186530450", -1, ""); throwable = new RuntimeException(className + ": " + messageWithStackTrace); } } catch (Throwable t) { throwable = new RuntimeException(className + ": " + messageWithStackTrace); throwable.addSuppressed(t); } throwable.setStackTrace(EMPTY_STACK_TRACE); Throwable cause = readThrowable(parcel); if (cause != null) { throwable.initCause(cause); } return throwable; } @Override @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) public int describeContents() { return 0; } public static final @NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { public AndroidFuture createFromParcel(Parcel parcel) { return new AndroidFuture(parcel); } public AndroidFuture[] newArray(int size) { return new AndroidFuture[size]; } }; }