blob: db262736e748e792ac8c3fa747a809eae88701d3 [file] [log] [blame]
/*
* Copyright 2018 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 androidx.concurrent.futures;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.common.util.concurrent.ListenableFuture;
import java.lang.ref.WeakReference;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* A utility that provides safety checks as an alternative to {@link
* androidx.concurrent.futures.ResolvableFuture}, failing the future if it will never complete.
* Useful for adapting interfaces that take callbacks into interfaces that return {@link
* ListenableFuture}.
*
* <p>Example:
*
* <pre>{@code
* return CallbackToFutureAdapter.getFuture(
* completer -> {
* Callback myCallback = foo -> completer.set(foo);
* someObject.getFoo(myCallback);
* return myCallback;
* });
* }</pre>
*
* Try to avoid creating references from listeners on the returned {@code Future} to the {@link
* Completer} or the passed-in {@code tag} object, as this will defeat the best-effort early failure
* detection based on garbage collection.
*/
public final class CallbackToFutureAdapter {
private CallbackToFutureAdapter() {
}
/*
* Returns a Future that will be completed by the {@link Completer} provided in from
* {@link Callback#getCompleter}.
*
* <p>The provided callback is invoked immediately inline. Any exceptions thrown by it will
* fail the returned {@code Future}.
*/
@NonNull
public static <T> ListenableFuture<T> getFuture(@NonNull Resolver<T> callback) {
Completer<T> completer = new Completer<>();
SafeFuture<T> safeFuture = new SafeFuture<>(completer);
completer.future = safeFuture;
// Set something as the tag, so that we can hopefully identify the call site from the
// toString()
// of the future. Retaining the instance could potentially cause a leak (if it's an inner
// class)
// and it's probably a lambda anyway so retaining the class provides just as much
// information.
completer.tag = callback.getClass();
// Start timeout before invoking the callback
final Object tag;
try {
tag = callback.attachCompleter(completer);
if (tag != null) {
completer.tag = tag;
}
} catch (Exception e) {
safeFuture.setException(e);
}
return safeFuture;
}
/** Called by {@link #getFuture}. */
public interface Resolver<T> {
/**
* Create your callback object and start whatever operations are required to trigger it
* here.
*
* @param completer Call one of the set methods on this object to complete the returned
* Future.
* @return an object to use as the human-readable description of what is expected to
* complete
* this future. In error cases, its toString() will be included in the message.
*/
@Nullable
Object attachCompleter(@NonNull Completer<T> completer) throws Exception;
}
// TODO(b/119308748): Implement InternalFutureFailureAccess
private static final class SafeFuture<T> implements ListenableFuture<T> {
final WeakReference<Completer<T>> completerWeakReference;
SafeFuture(Completer<T> completer) {
this.completerWeakReference = new WeakReference<>(completer);
}
private final AbstractResolvableFuture<T> delegate = new AbstractResolvableFuture<T>() {
@Override
protected String pendingToString() {
Completer<T> completer = completerWeakReference.get();
if (completer == null) {
return "Completer object has been garbage collected, future will fail soon";
} else {
return "tag=[" + completer.tag + "]";
}
}
};
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
// Obtain reference to completer before setting; a listener might make completer weakly
// weakly reachable.
Completer<T> completer = completerWeakReference.get();
boolean cancelled = delegate.cancel(mayInterruptIfRunning);
if (cancelled && completer != null) {
// If the completer was null here, that means it will be finalized in the future.
completer.fireCancellationListeners();
}
return cancelled;
}
boolean cancelWithoutNotifyingCompleter(boolean shouldInterrupt) {
return delegate.cancel(shouldInterrupt);
}
// setFuture intentionally omitted, because it interacts badly with timeouts
boolean set(T value) {
return delegate.set(value);
}
boolean setException(Throwable t) {
return delegate.setException(t);
}
@Override
public boolean isCancelled() {
return delegate.isCancelled();
}
@Override
public boolean isDone() {
return delegate.isDone();
}
@Override
public T get() throws InterruptedException, ExecutionException {
return delegate.get();
}
@Override
public T get(long timeout, @NonNull TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return delegate.get(timeout, unit);
}
@Override
public void addListener(@NonNull Runnable listener, @NonNull Executor executor) {
delegate.addListener(listener, executor);
}
@Override
public String toString() {
return delegate.toString();
}
}
/** Used to complete the future returned by {@link #getFuture} */
public static final class Completer<T> {
// synthetic access
Object tag;
// synthetic access
SafeFuture<T> future;
private ResolvableFuture<Void> cancellationFuture = ResolvableFuture.create();
/**
* Tracks whether a caller ever attempted to complete the future. If they did, we won't
* invoke
* cancellation listeners if this object is GCed.
*/
private boolean attemptedSetting;
Completer() {
}
/**
* Sets the result of the {@code Future} unless the {@code Future} has already been
* cancelled or
* set. When a call to this method returns, the {@code Future} is guaranteed to be done.
*
* @param value the value to be used as the result
* @return true if this attempt completed the {@code Future}, false if it was already
* complete
*/
public boolean set(T value) {
attemptedSetting = true;
SafeFuture<T> localFuture = future;
boolean wasSet = localFuture != null && localFuture.set(value);
if (wasSet) {
setCompletedNormally();
}
return wasSet;
}
/**
* Sets the failed result of the {@code Future} unless the {@code Future} has already been
* cancelled or set. When a call to this method returns, the {@code Future} is guaranteed
* to be
* done.
*
* @param t the exception to be used as the failed result
* @return true if this attempt completed the {@code Future}, false if it was already
* complete
*/
public boolean setException(@NonNull Throwable t) {
attemptedSetting = true;
SafeFuture<T> localFuture = future;
boolean wasSet = localFuture != null && localFuture.setException(t);
if (wasSet) {
setCompletedNormally();
}
return wasSet;
}
/**
* Cancels {@code Future} unless the {@code Future} has already been cancelled or set.
* When a
* call to this method returns, the {@code Future} is guaranteed to be done.
*
* @return true if this attempt completed the {@code Future}, false if it was already
* complete
*/
public boolean setCancelled() {
attemptedSetting = true;
SafeFuture<T> localFuture = future;
boolean wasSet = localFuture != null && localFuture.cancelWithoutNotifyingCompleter(
true);
if (wasSet) {
setCompletedNormally();
}
return wasSet;
}
/**
* Use to propagate cancellation from the future to whatever operation is using this
* Completer.
* <p>
* Will be called when the returned Future is cancelled by
* {@link Future#cancel(boolean)} or this {@code Completer} object is garbage collected
* before the future completes.
* Not triggered by {@link #setCancelled}.
*/
public void addCancellationListener(@NonNull Runnable runnable,
@NonNull Executor executor) {
ListenableFuture<?> localCancellationFuture = cancellationFuture;
if (localCancellationFuture != null) {
localCancellationFuture.addListener(runnable, executor);
}
}
void fireCancellationListeners() {
tag = null;
future = null;
cancellationFuture.set(null);
}
private void setCompletedNormally() {
// Null out, so that GC does not retain the future and its value even if the callback
// retains
// the completer object
tag = null;
future = null;
cancellationFuture = null;
}
// toString intentionally left omitted, so that if the tag object (which holds this object
// as a field) includes it in its toString, we won't infinitely recurse.
@Override
protected void finalize() {
SafeFuture<T> localFuture = future;
// Complete the future with an error before any cancellation listeners try to set the
// future.
// Also avoid allocating the exception if we know we won't actually be able to set it.
if (localFuture != null && !localFuture.isDone()) {
localFuture.setException(
new FutureGarbageCollectedException(
"The completer object was garbage collected - this future would "
+ "otherwise never "
+ "complete. The tag was: "
+ tag));
}
if (!attemptedSetting) {
ResolvableFuture<Void> localCancellationFuture = cancellationFuture;
if (localCancellationFuture != null) {
// set is idempotent, so even if this was already invoked it won't run
// listeners twice
localCancellationFuture.set(null);
}
}
}
}
static final class FutureGarbageCollectedException extends Throwable {
FutureGarbageCollectedException(String message) {
super(message);
}
@Override
public synchronized Throwable fillInStackTrace() {
return this; // no stack trace, wouldn't be useful anyway
}
}
}