Introduce listenablefuture and failureaccess artifacts, plus InternalFutureFailureAccess.
(taken over from CL 210155310 to add Maven setup)
It provides a direct access to the cause of any failures,
so we can avoid unnecessary allocation of an exception.
Design discussion: https://docs.google.com/document/d/1_RVTtztq5pqrhs0srvJWHMI7PT1tA71--iaauV2l5UA/edit
RELNOTES=Created separate `listenablefuture` and `failureaccess` artifacts, the latter containing the new `InternalFutureFailureAccess`. For more details about `listenablefuture`, see [this announcement](https://groups.google.com/d/topic/guava-announce/Km82fZG68Sw).
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=212516713
diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureCancellationCauseTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureCancellationCauseTest.java
index 380f69f..6c921b5 100644
--- a/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureCancellationCauseTest.java
+++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureCancellationCauseTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
@@ -34,6 +35,8 @@
private ClassLoader oldClassLoader;
private URLClassLoader classReloader;
+ private Class<?> settableFutureClass;
+ private Class<?> abstractFutureClass;
@Override
protected void setUp() throws Exception {
@@ -68,6 +71,8 @@
};
oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classReloader);
+ abstractFutureClass = classReloader.loadClass(AbstractFuture.class.getName());
+ settableFutureClass = classReloader.loadClass(SettableFuture.class.getName());
}
@Override
@@ -82,6 +87,7 @@
assertTrue(future.cancel(false));
assertTrue(future.isCancelled());
assertTrue(future.isDone());
+ assertNull(tryInternalFastPathGetFailure(future));
try {
future.get();
fail("Expected CancellationException");
@@ -95,6 +101,7 @@
assertTrue(future.cancel(true));
assertTrue(future.isCancelled());
assertTrue(future.isDone());
+ assertNull(tryInternalFastPathGetFailure(future));
try {
future.get();
fail("Expected CancellationException");
@@ -153,7 +160,13 @@
}
private Future<?> newFutureInstance() throws Exception {
- return (Future<?>)
- classReloader.loadClass(SettableFuture.class.getName()).getMethod("create").invoke(null);
+ return (Future<?>) settableFutureClass.getMethod("create").invoke(null);
+ }
+
+ private Throwable tryInternalFastPathGetFailure(Future<?> future) throws Exception {
+ Method tryInternalFastPathGetFailureMethod =
+ abstractFutureClass.getDeclaredMethod("tryInternalFastPathGetFailure");
+ tryInternalFastPathGetFailureMethod.setAccessible(true);
+ return (Throwable) tryInternalFastPathGetFailureMethod.invoke(future);
}
}
diff --git a/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java
index c850032..2bdbef3 100644
--- a/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java
+++ b/android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java
@@ -22,6 +22,7 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.internal.InternalFutureFailureAccess;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -971,6 +972,113 @@
t.join();
}
+ public void testTrustedGetFailure_Completed() {
+ SettableFuture<String> future = SettableFuture.create();
+ future.set("261");
+ assertThat(future.tryInternalFastPathGetFailure()).isNull();
+ }
+
+ public void testTrustedGetFailure_Failed() {
+ SettableFuture<String> future = SettableFuture.create();
+ Throwable failure = new Throwable();
+ future.setException(failure);
+ assertThat(future.tryInternalFastPathGetFailure()).isEqualTo(failure);
+ }
+
+ public void testTrustedGetFailure_NotCompleted() {
+ SettableFuture<String> future = SettableFuture.create();
+ assertThat(future.isDone()).isFalse();
+ assertThat(future.tryInternalFastPathGetFailure()).isNull();
+ }
+
+ public void testTrustedGetFailure_CanceledNoCause() {
+ SettableFuture<String> future = SettableFuture.create();
+ future.cancel(false);
+ assertThat(future.tryInternalFastPathGetFailure()).isNull();
+ }
+
+ public void testGetFailure_Completed() {
+ AbstractFuture<String> future = new AbstractFuture<String>() {};
+ future.set("261");
+ assertThat(future.tryInternalFastPathGetFailure()).isNull();
+ }
+
+ public void testGetFailure_Failed() {
+ AbstractFuture<String> future = new AbstractFuture<String>() {};
+ final Throwable failure = new Throwable();
+ future.setException(failure);
+ assertThat(future.tryInternalFastPathGetFailure()).isNull();
+ }
+
+ public void testGetFailure_NotCompleted() {
+ AbstractFuture<String> future = new AbstractFuture<String>() {};
+ assertThat(future.isDone()).isFalse();
+ assertThat(future.tryInternalFastPathGetFailure()).isNull();
+ }
+
+ public void testGetFailure_CanceledNoCause() {
+ AbstractFuture<String> future = new AbstractFuture<String>() {};
+ future.cancel(false);
+ assertThat(future.tryInternalFastPathGetFailure()).isNull();
+ }
+
+ public void testForwardExceptionFastPath() throws Exception {
+ class FailFuture extends InternalFutureFailureAccess implements ListenableFuture<String> {
+ Throwable failure;
+
+ FailFuture(Throwable throwable) {
+ failure = throwable;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ throw new AssertionFailedError("cancel shouldn't be called on this object");
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public boolean isDone() {
+ return true;
+ }
+
+ @Override
+ public String get() throws InterruptedException, ExecutionException {
+ throw new AssertionFailedError("get() shouldn't be called on this object");
+ }
+
+ @Override
+ public String get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return get();
+ }
+
+ @Override
+ protected Throwable tryInternalFastPathGetFailure() {
+ return failure;
+ }
+
+ @Override
+ public void addListener(Runnable listener, Executor executor) {
+ throw new AssertionFailedError("addListener() shouldn't be called on this object");
+ }
+ }
+
+ final RuntimeException exception = new RuntimeException("you still didn't say the magic word!");
+ SettableFuture<String> normalFuture = SettableFuture.create();
+ normalFuture.setFuture(new FailFuture(exception));
+ assertTrue(normalFuture.isDone());
+ try {
+ normalFuture.get();
+ fail();
+ } catch (ExecutionException e) {
+ assertSame(exception, e.getCause());
+ }
+ }
+
private static void awaitUnchecked(final CyclicBarrier barrier) {
try {
barrier.await();
diff --git a/android/guava/pom.xml b/android/guava/pom.xml
index f014878..f4dcddb 100644
--- a/android/guava/pom.xml
+++ b/android/guava/pom.xml
@@ -17,6 +17,16 @@
</description>
<dependencies>
<dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>failureaccess</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>listenablefuture</artifactId>
+ <version>9999.0-empty-to-avoid-conflict-with-guava</version>
+ </dependency>
+ <dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
diff --git a/android/guava/src/com/google/common/util/concurrent/AbstractFuture.java b/android/guava/src/com/google/common/util/concurrent/AbstractFuture.java
index 564888b..740c58d 100644
--- a/android/guava/src/com/google/common/util/concurrent/AbstractFuture.java
+++ b/android/guava/src/com/google/common/util/concurrent/AbstractFuture.java
@@ -20,6 +20,8 @@
import com.google.common.annotations.Beta;
import com.google.common.annotations.GwtCompatible;
+import com.google.common.util.concurrent.internal.InternalFutureFailureAccess;
+import com.google.common.util.concurrent.internal.InternalFutures;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.ForOverride;
import com.google.j2objc.annotations.ReflectionSupport;
@@ -62,7 +64,8 @@
@SuppressWarnings("ShortCircuitBoolean") // we use non-short circuiting comparisons intentionally
@GwtCompatible(emulated = true)
@ReflectionSupport(value = ReflectionSupport.Level.FULL)
-public abstract class AbstractFuture<V> implements ListenableFuture<V> {
+public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
+ implements ListenableFuture<V> {
// NOTE: Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, ||
private static final boolean GENERATE_CANCELLATION_CAUSES =
@@ -847,6 +850,13 @@
}
return v;
}
+ if (future instanceof InternalFutureFailureAccess) {
+ Throwable throwable =
+ InternalFutures.tryInternalFastPathGetFailure((InternalFutureFailureAccess) future);
+ if (throwable != null) {
+ return new Failure(throwable);
+ }
+ }
boolean wasCancelled = future.isCancelled();
// Don't allocate a CancellationException if it's not necessary
if (!GENERATE_CANCELLATION_CAUSES & wasCancelled) {
@@ -967,6 +977,39 @@
@ForOverride
protected void afterDone() {}
+ // TODO(b/114236866): Inherit doc from InternalFutureFailureAccess. Also, -link to its URL.
+ /**
+ * Usually returns {@code null} but, if this {@code Future} has failed, may <i>optionally</i>
+ * return the cause of the failure. "Failure" means specifically "completed with an exception"; it
+ * does not include "was cancelled." To be explicit: If this method returns a non-null value,
+ * then:
+ *
+ * <ul>
+ * <li>{@code isDone()} must return {@code true}
+ * <li>{@code isCancelled()} must return {@code false}
+ * <li>{@code get()} must not block, and it must throw an {@code ExecutionException} with the
+ * return value of this method as its cause
+ * </ul>
+ *
+ * <p>This method is {@code protected} so that classes like {@code
+ * com.google.common.util.concurrent.SettableFuture} do not expose it to their users as an
+ * instance method. In the unlikely event that you need to call this method, call {@link
+ * InternalFutures#tryInternalFastPathGetFailure(InternalFutureFailureAccess)}.
+ *
+ * @since 27.0
+ */
+ @Override
+ @NullableDecl
+ protected final Throwable tryInternalFastPathGetFailure() {
+ if (this instanceof Trusted) {
+ Object obj = value;
+ if (obj instanceof Failure) {
+ return ((Failure) obj).exception;
+ }
+ }
+ return null;
+ }
+
/**
* Returns the exception that this {@code Future} completed with. This includes completion through
* a call to {@link #setException} or {@link #setFuture setFuture}{@code (failedFuture)} but not
diff --git a/android/guava/src/com/google/common/util/concurrent/ListenableFuture.java b/android/guava/src/com/google/common/util/concurrent/ListenableFuture.java
index 5c56973..33b56d6 100644
--- a/android/guava/src/com/google/common/util/concurrent/ListenableFuture.java
+++ b/android/guava/src/com/google/common/util/concurrent/ListenableFuture.java
@@ -14,7 +14,6 @@
package com.google.common.util.concurrent;
-import com.google.common.annotations.GwtCompatible;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
@@ -29,6 +28,8 @@
* href="https://github.com/google/guava/wiki/ListenableFutureExplained">{@code
* ListenableFuture}</a>.
*
+ * <p>This class is GWT-compatible.
+ *
* <h3>Purpose</h3>
*
* <p>The main purpose of {@code ListenableFuture} is to help you chain together a graph of
@@ -97,7 +98,6 @@
* @author Nishant Thakkar
* @since 1.0
*/
-@GwtCompatible
public interface ListenableFuture<V> extends Future<V> {
/**
* Registers a listener to be {@linkplain Executor#execute(Runnable) run} on the given executor.
diff --git a/futures/README.md b/futures/README.md
new file mode 100644
index 0000000..7430ebd
--- /dev/null
+++ b/futures/README.md
@@ -0,0 +1,2 @@
+The modules under this directory will be released exactly once each. Once that
+happens, there will be no need to ever update any files here.
diff --git a/futures/failureaccess/pom.xml b/futures/failureaccess/pom.xml
new file mode 100644
index 0000000..01ce092
--- /dev/null
+++ b/futures/failureaccess/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava-parent</artifactId>
+ <version>26.0-android</version>
+ </parent>
+ <artifactId>failureaccess</artifactId>
+ <version>1.0</version>
+ <name>Guava InternalFutureFailureAccess and InternalFutures</name>
+ <description>
+ Contains
+ com.google.common.util.concurrent.internal.InternalFutureFailureAccess and
+ InternalFutures. Most users will never need to use this artifact. Its
+ classes is conceptually a part of Guava, but they're in this separate
+ artifact so that Android libraries can use them without pulling in all of
+ Guava (just as they can use ListenableFuture by depending on the
+ listenablefuture artifact).
+ </description>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-source-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>animal-sniffer-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-docs</id>
+ </execution>
+ <execution>
+ <id>generate-javadoc-site-report</id>
+ <phase>site</phase>
+ <goals><goal>javadoc</goal></goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/futures/failureaccess/src/com/google/common/util/concurrent/internal/InternalFutureFailureAccess.java b/futures/failureaccess/src/com/google/common/util/concurrent/internal/InternalFutureFailureAccess.java
new file mode 100644
index 0000000..7cc8489
--- /dev/null
+++ b/futures/failureaccess/src/com/google/common/util/concurrent/internal/InternalFutureFailureAccess.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 The Guava Authors
+ *
+ * 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.google.common.util.concurrent.internal;
+
+/**
+ * A future that, if it fails, may <i>optionally</i> provide access to the cause of the failure.
+ *
+ * <p>This class is used only for micro-optimization. Standard {@code Future} utilities benefit from
+ * this optimization, so there is no need to specialize methods to return or accept this type
+ * instead of {@code ListenableFuture}.
+ *
+ * <p>This class is GWT-compatible.
+ *
+ * @since {@code com.google.guava:failureaccess:1.0}, which was added as a dependency of Guava in
+ * Guava 27.0
+ */
+public abstract class InternalFutureFailureAccess {
+ /** Constructor for use by subclasses. */
+ protected InternalFutureFailureAccess() {}
+
+ /**
+ * Usually returns {@code null} but, if this {@code Future} has failed, may <i>optionally</i>
+ * return the cause of the failure. "Failure" means specifically "completed with an exception"; it
+ * does not include "was cancelled." To be explicit: If this method returns a non-null value,
+ * then:
+ *
+ * <ul>
+ * <li>{@code isDone()} must return {@code true}
+ * <li>{@code isCancelled()} must return {@code false}
+ * <li>{@code get()} must not block, and it must throw an {@code ExecutionException} with the
+ * return value of this method as its cause
+ * </ul>
+ *
+ * <p>This method is {@code protected} so that classes like {@code
+ * com.google.common.util.concurrent.SettableFuture} do not expose it to their users as an
+ * instance method. In the unlikely event that you need to call this method, call {@link
+ * InternalFutures#tryInternalFastPathGetFailure(InternalFutureFailureAccess)}.
+ */
+ protected abstract Throwable tryInternalFastPathGetFailure();
+}
diff --git a/futures/failureaccess/src/com/google/common/util/concurrent/internal/InternalFutures.java b/futures/failureaccess/src/com/google/common/util/concurrent/internal/InternalFutures.java
new file mode 100644
index 0000000..42df5ec
--- /dev/null
+++ b/futures/failureaccess/src/com/google/common/util/concurrent/internal/InternalFutures.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 The Guava Authors
+ *
+ * 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.google.common.util.concurrent.internal;
+
+/**
+ * Static utilities for {@link InternalFutureFailureAccess}. Most users will never need to use this
+ * class.
+ *
+ * <p>This class is GWT-compatible.
+ *
+ * @since {@code com.google.guava:failureaccess:1.0}, which was added as a dependency of Guava in
+ * Guava 27.0
+ */
+public final class InternalFutures {
+ /**
+ * Usually returns {@code null} but, if the given {@code Future} has failed, may <i>optionally</i>
+ * return the cause of the failure. "Failure" means specifically "completed with an exception"; it
+ * does not include "was cancelled." To be explicit: If this method returns a non-null value,
+ * then:
+ *
+ * <ul>
+ * <li>{@code isDone()} must return {@code true}
+ * <li>{@code isCancelled()} must return {@code false}
+ * <li>{@code get()} must not block, and it must throw an {@code ExecutionException} with the
+ * return value of this method as its cause
+ * </ul>
+ */
+ public static Throwable tryInternalFastPathGetFailure(InternalFutureFailureAccess future) {
+ return future.tryInternalFastPathGetFailure();
+ }
+
+ private InternalFutures() {}
+}
diff --git a/futures/listenablefuture1/pom.xml b/futures/listenablefuture1/pom.xml
new file mode 100644
index 0000000..3bd8c01
--- /dev/null
+++ b/futures/listenablefuture1/pom.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava-parent</artifactId>
+ <version>26.0-android</version>
+ </parent>
+ <artifactId>listenablefuture</artifactId>
+ <version>1.0</version>
+ <name>Guava ListenableFuture only</name>
+ <description>
+ Contains Guava's com.google.common.util.concurrent.ListenableFuture class,
+ without any of its other classes -- but is also available in a second
+ "version" that omits the class to avoid conflicts with the copy in Guava
+ itself. The idea is:
+
+ - If users want only ListenableFuture, they depend on listenablefuture-1.0.
+
+ - If users want all of Guava, they depend on guava, which, as of Guava
+ 27.0, depends on
+ listenablefuture-9999.0-empty-to-avoid-conflict-with-guava. The 9999.0-...
+ version number is enough for some build systems (notably, Gradle) to select
+ that empty artifact over the "real" listenablefuture-1.0 -- avoiding a
+ conflict with the copy of ListenableFuture in guava itself. If users are
+ using an older version of Guava or a build system other than Gradle, they
+ may see class conflicts. If so, they can solve them by manually excluding
+ the listenablefuture artifact or manually forcing their build systems to
+ use 9999.0-....
+ </description>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-source-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>animal-sniffer-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-docs</id>
+ </execution>
+ <execution>
+ <id>generate-javadoc-site-report</id>
+ <phase>site</phase>
+ <goals><goal>javadoc</goal></goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/futures/listenablefuture1/src/com/google/common/util/concurrent/ListenableFuture.java b/futures/listenablefuture1/src/com/google/common/util/concurrent/ListenableFuture.java
new file mode 100644
index 0000000..33b56d6
--- /dev/null
+++ b/futures/listenablefuture1/src/com/google/common/util/concurrent/ListenableFuture.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2007 The Guava Authors
+ *
+ * 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.google.common.util.concurrent;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * A {@link Future} that accepts completion listeners. Each listener has an associated executor, and
+ * it is invoked using this executor once the future's computation is {@linkplain Future#isDone()
+ * complete}. If the computation has already completed when the listener is added, the listener will
+ * execute immediately.
+ *
+ * <p>See the Guava User Guide article on <a
+ * href="https://github.com/google/guava/wiki/ListenableFutureExplained">{@code
+ * ListenableFuture}</a>.
+ *
+ * <p>This class is GWT-compatible.
+ *
+ * <h3>Purpose</h3>
+ *
+ * <p>The main purpose of {@code ListenableFuture} is to help you chain together a graph of
+ * asynchronous operations. You can chain them together manually with calls to methods like {@link
+ * Futures#transform(ListenableFuture, com.google.common.base.Function, Executor)
+ * Futures.transform}, but you will often find it easier to use a framework. Frameworks automate the
+ * process, often adding features like monitoring, debugging, and cancellation. Examples of
+ * frameworks include:
+ *
+ * <ul>
+ * <li><a href="http://google.github.io/dagger/producers.html">Dagger Producers</a>
+ * </ul>
+ *
+ * <p>The main purpose of {@link #addListener addListener} is to support this chaining. You will
+ * rarely use it directly, in part because it does not provide direct access to the {@code Future}
+ * result. (If you want such access, you may prefer {@link Futures#addCallback
+ * Futures.addCallback}.) Still, direct {@code addListener} calls are occasionally useful:
+ *
+ * <pre>{@code
+ * final String name = ...;
+ * inFlight.add(name);
+ * ListenableFuture<Result> future = service.query(name);
+ * future.addListener(new Runnable() {
+ * public void run() {
+ * processedCount.incrementAndGet();
+ * inFlight.remove(name);
+ * lastProcessed.set(name);
+ * logger.info("Done with {0}", name);
+ * }
+ * }, executor);
+ * }</pre>
+ *
+ * <h3>How to get an instance</h3>
+ *
+ * <p>We encourage you to return {@code ListenableFuture} from your methods so that your users can
+ * take advantage of the {@linkplain Futures utilities built atop the class}. The way that you will
+ * create {@code ListenableFuture} instances depends on how you currently create {@code Future}
+ * instances:
+ *
+ * <ul>
+ * <li>If you receive them from an {@code java.util.concurrent.ExecutorService}, convert that
+ * service to a {@link ListeningExecutorService}, usually by calling {@link
+ * MoreExecutors#listeningDecorator(java.util.concurrent.ExecutorService)
+ * MoreExecutors.listeningDecorator}.
+ * <li>If you manually call {@link java.util.concurrent.FutureTask#set} or a similar method,
+ * create a {@link SettableFuture} instead. (If your needs are more complex, you may prefer
+ * {@link AbstractFuture}.)
+ * </ul>
+ *
+ * <p><b>Test doubles</b>: If you need a {@code ListenableFuture} for your test, try a {@link
+ * SettableFuture} or one of the methods in the {@link Futures#immediateFuture Futures.immediate*}
+ * family. <b>Avoid</b> creating a mock or stub {@code Future}. Mock and stub implementations are
+ * fragile because they assume that only certain methods will be called and because they often
+ * implement subtleties of the API improperly.
+ *
+ * <p><b>Custom implementation</b>: Avoid implementing {@code ListenableFuture} from scratch. If you
+ * can't get by with the standard implementations, prefer to derive a new {@code Future} instance
+ * with the methods in {@link Futures} or, if necessary, to extend {@link AbstractFuture}.
+ *
+ * <p>Occasionally, an API will return a plain {@code Future} and it will be impossible to change
+ * the return type. For this case, we provide a more expensive workaround in {@code
+ * JdkFutureAdapters}. However, when possible, it is more efficient and reliable to create a {@code
+ * ListenableFuture} directly.
+ *
+ * @author Sven Mawson
+ * @author Nishant Thakkar
+ * @since 1.0
+ */
+public interface ListenableFuture<V> extends Future<V> {
+ /**
+ * Registers a listener to be {@linkplain Executor#execute(Runnable) run} on the given executor.
+ * The listener will run when the {@code Future}'s computation is {@linkplain Future#isDone()
+ * complete} or, if the computation is already complete, immediately.
+ *
+ * <p>There is no guaranteed ordering of execution of listeners, but any listener added through
+ * this method is guaranteed to be called once the computation is complete.
+ *
+ * <p>Exceptions thrown by a listener will be propagated up to the executor. Any exception thrown
+ * during {@code Executor.execute} (e.g., a {@code RejectedExecutionException} or an exception
+ * thrown by {@linkplain MoreExecutors#directExecutor direct execution}) will be caught and
+ * logged.
+ *
+ * <p>Note: For fast, lightweight listeners that would be safe to execute in any thread, consider
+ * {@link MoreExecutors#directExecutor}. Otherwise, avoid it. Heavyweight {@code directExecutor}
+ * listeners can cause problems, and these problems can be difficult to reproduce because they
+ * depend on timing. For example:
+ *
+ * <ul>
+ * <li>The listener may be executed by the caller of {@code addListener}. That caller may be a
+ * UI thread or other latency-sensitive thread. This can harm UI responsiveness.
+ * <li>The listener may be executed by the thread that completes this {@code Future}. That
+ * thread may be an internal system thread such as an RPC network thread. Blocking that
+ * thread may stall progress of the whole system. It may even cause a deadlock.
+ * <li>The listener may delay other listeners, even listeners that are not themselves {@code
+ * directExecutor} listeners.
+ * </ul>
+ *
+ * <p>This is the most general listener interface. For common operations performed using
+ * listeners, see {@link Futures}. For a simplified but general listener interface, see {@link
+ * Futures#addCallback addCallback()}.
+ *
+ * <p>Memory consistency effects: Actions in a thread prior to adding a listener <a
+ * href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5">
+ * <i>happen-before</i></a> its execution begins, perhaps in another thread.
+ *
+ * @param listener the listener to run when the computation is complete
+ * @param executor the executor to run the listener in
+ * @throws RejectedExecutionException if we tried to execute the listener immediately but the
+ * executor rejected it.
+ */
+ void addListener(Runnable listener, Executor executor);
+}
diff --git a/futures/listenablefuture9999/pom.xml b/futures/listenablefuture9999/pom.xml
new file mode 100644
index 0000000..ad3f23e
--- /dev/null
+++ b/futures/listenablefuture9999/pom.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava-parent</artifactId>
+ <version>26.0-android</version>
+ </parent>
+ <artifactId>listenablefuture</artifactId>
+ <version>9999.0-empty-to-avoid-conflict-with-guava</version>
+ <name>Guava ListenableFuture only</name>
+ <description>
+ An empty artifact that Guava depends on to signal that it is providing
+ ListenableFuture -- but is also available in a second "version" that
+ contains com.google.common.util.concurrent.ListenableFuture class, without
+ any other Guava classes. The idea is:
+
+ - If users want only ListenableFuture, they depend on listenablefuture-1.0.
+
+ - If users want all of Guava, they depend on guava, which, as of Guava
+ 27.0, depends on
+ listenablefuture-9999.0-empty-to-avoid-conflict-with-guava. The 9999.0-...
+ version number is enough for some build systems (notably, Gradle) to select
+ that empty artifact over the "real" listenablefuture-1.0 -- avoiding a
+ conflict with the copy of ListenableFuture in guava itself. If users are
+ using an older version of Guava or a build system other than Gradle, they
+ may see class conflicts. If so, they can solve them by manually excluding
+ the listenablefuture artifact or manually forcing their build systems to
+ use 9999.0-....
+ </description>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-source-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>animal-sniffer-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-docs</id>
+ </execution>
+ <execution>
+ <id>generate-javadoc-site-report</id>
+ <phase>site</phase>
+ <goals><goal>javadoc</goal></goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/guava-gwt/pom.xml b/guava-gwt/pom.xml
index a20c034..5fa6c42 100644
--- a/guava-gwt/pom.xml
+++ b/guava-gwt/pom.xml
@@ -46,6 +46,11 @@
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
+ <artifactId>failureaccess</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${project.version}</version>
</dependency>
@@ -125,6 +130,20 @@
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
+ <id>unpack-failureaccess-sources</id>
+ <phase>generate-resources</phase>
+ <goals><goal>unpack-dependencies</goal></goals>
+ <configuration>
+ <includeArtifactIds>failureaccess</includeArtifactIds>
+ <classifier>sources</classifier>
+ <excludeTransitive>true</excludeTransitive>
+ <excludes>META-INF/MANIFEST.MF</excludes>
+ <outputDirectory>${project.build.directory}/failureaccess-sources</outputDirectory>
+ <type>java-source</type>
+ <silent>false</silent>
+ </configuration>
+ </execution>
+ <execution>
<id>unpack-guava-sources</id>
<phase>generate-resources</phase>
<goals><goal>unpack-dependencies</goal></goals>
@@ -184,6 +203,12 @@
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
+ <artifactId>failureaccess</artifactId>
+ <version>1.0</version>
+ <classifier>sources</classifier>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${project.version}</version>
<classifier>sources</classifier>
@@ -222,6 +247,22 @@
<contains text="@GwtCompatible"/>
</fileset>
</copy>
+ <!-- The following don't contain @GwtCompatible for dependency reasons. -->
+ <copy toDir="${project.build.directory}/guava-gwt-sources">
+ <fileset dir="${project.build.directory}/guava-sources">
+ <include name="**/ListenableFuture.java" />
+ </fileset>
+ </copy>
+ <copy toDir="${project.build.directory}/guava-gwt-sources">
+ <fileset dir="${project.build.directory}/failureaccess-sources">
+ <include name="**/InternalFutures.java" />
+ </fileset>
+ </copy>
+ <copy toDir="${project.build.directory}/guava-gwt-sources">
+ <fileset dir="${project.build.directory}/failureaccess-sources">
+ <include name="**/InternalFutureFailureAccess.java" />
+ </fileset>
+ </copy>
<copy toDir="${project.build.directory}/guava-test-gwt-sources">
<fileset dir="${project.build.directory}/guava-test-sources">
<contains text="@GwtCompatible"/>
diff --git a/guava-gwt/src-super/com/google/common/util/concurrent/super/com/google/common/util/concurrent/AbstractFuture.java b/guava-gwt/src-super/com/google/common/util/concurrent/super/com/google/common/util/concurrent/AbstractFuture.java
index bc05340..96691b8 100644
--- a/guava-gwt/src-super/com/google/common/util/concurrent/super/com/google/common/util/concurrent/AbstractFuture.java
+++ b/guava-gwt/src-super/com/google/common/util/concurrent/super/com/google/common/util/concurrent/AbstractFuture.java
@@ -22,6 +22,7 @@
import static com.google.common.util.concurrent.Futures.getDone;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+import com.google.common.util.concurrent.internal.InternalFutureFailureAccess;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
@@ -35,7 +36,8 @@
import org.checkerframework.checker.nullness.qual.Nullable;
/** Emulation for AbstractFuture in GWT. */
-public abstract class AbstractFuture<V> implements ListenableFuture<V> {
+public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
+ implements ListenableFuture<V> {
/**
* Tag interface marking trusted subclasses. This enables some optimizations. The implementation
@@ -215,6 +217,11 @@
protected void afterDone() {}
+ @Override
+ protected final Throwable tryInternalFastPathGetFailure() {
+ return state == State.FAILURE ? throwable : null;
+ }
+
final Throwable trustedGetException() {
checkState(state == State.FAILURE);
return throwable;
diff --git a/guava-tests/test/com/google/common/util/concurrent/AbstractFutureCancellationCauseTest.java b/guava-tests/test/com/google/common/util/concurrent/AbstractFutureCancellationCauseTest.java
index 380f69f..6c921b5 100644
--- a/guava-tests/test/com/google/common/util/concurrent/AbstractFutureCancellationCauseTest.java
+++ b/guava-tests/test/com/google/common/util/concurrent/AbstractFutureCancellationCauseTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import java.lang.reflect.Method;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
@@ -34,6 +35,8 @@
private ClassLoader oldClassLoader;
private URLClassLoader classReloader;
+ private Class<?> settableFutureClass;
+ private Class<?> abstractFutureClass;
@Override
protected void setUp() throws Exception {
@@ -68,6 +71,8 @@
};
oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classReloader);
+ abstractFutureClass = classReloader.loadClass(AbstractFuture.class.getName());
+ settableFutureClass = classReloader.loadClass(SettableFuture.class.getName());
}
@Override
@@ -82,6 +87,7 @@
assertTrue(future.cancel(false));
assertTrue(future.isCancelled());
assertTrue(future.isDone());
+ assertNull(tryInternalFastPathGetFailure(future));
try {
future.get();
fail("Expected CancellationException");
@@ -95,6 +101,7 @@
assertTrue(future.cancel(true));
assertTrue(future.isCancelled());
assertTrue(future.isDone());
+ assertNull(tryInternalFastPathGetFailure(future));
try {
future.get();
fail("Expected CancellationException");
@@ -153,7 +160,13 @@
}
private Future<?> newFutureInstance() throws Exception {
- return (Future<?>)
- classReloader.loadClass(SettableFuture.class.getName()).getMethod("create").invoke(null);
+ return (Future<?>) settableFutureClass.getMethod("create").invoke(null);
+ }
+
+ private Throwable tryInternalFastPathGetFailure(Future<?> future) throws Exception {
+ Method tryInternalFastPathGetFailureMethod =
+ abstractFutureClass.getDeclaredMethod("tryInternalFastPathGetFailure");
+ tryInternalFastPathGetFailureMethod.setAccessible(true);
+ return (Throwable) tryInternalFastPathGetFailureMethod.invoke(future);
}
}
diff --git a/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java b/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java
index c850032..2bdbef3 100644
--- a/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java
+++ b/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java
@@ -22,6 +22,7 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.internal.InternalFutureFailureAccess;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -971,6 +972,113 @@
t.join();
}
+ public void testTrustedGetFailure_Completed() {
+ SettableFuture<String> future = SettableFuture.create();
+ future.set("261");
+ assertThat(future.tryInternalFastPathGetFailure()).isNull();
+ }
+
+ public void testTrustedGetFailure_Failed() {
+ SettableFuture<String> future = SettableFuture.create();
+ Throwable failure = new Throwable();
+ future.setException(failure);
+ assertThat(future.tryInternalFastPathGetFailure()).isEqualTo(failure);
+ }
+
+ public void testTrustedGetFailure_NotCompleted() {
+ SettableFuture<String> future = SettableFuture.create();
+ assertThat(future.isDone()).isFalse();
+ assertThat(future.tryInternalFastPathGetFailure()).isNull();
+ }
+
+ public void testTrustedGetFailure_CanceledNoCause() {
+ SettableFuture<String> future = SettableFuture.create();
+ future.cancel(false);
+ assertThat(future.tryInternalFastPathGetFailure()).isNull();
+ }
+
+ public void testGetFailure_Completed() {
+ AbstractFuture<String> future = new AbstractFuture<String>() {};
+ future.set("261");
+ assertThat(future.tryInternalFastPathGetFailure()).isNull();
+ }
+
+ public void testGetFailure_Failed() {
+ AbstractFuture<String> future = new AbstractFuture<String>() {};
+ final Throwable failure = new Throwable();
+ future.setException(failure);
+ assertThat(future.tryInternalFastPathGetFailure()).isNull();
+ }
+
+ public void testGetFailure_NotCompleted() {
+ AbstractFuture<String> future = new AbstractFuture<String>() {};
+ assertThat(future.isDone()).isFalse();
+ assertThat(future.tryInternalFastPathGetFailure()).isNull();
+ }
+
+ public void testGetFailure_CanceledNoCause() {
+ AbstractFuture<String> future = new AbstractFuture<String>() {};
+ future.cancel(false);
+ assertThat(future.tryInternalFastPathGetFailure()).isNull();
+ }
+
+ public void testForwardExceptionFastPath() throws Exception {
+ class FailFuture extends InternalFutureFailureAccess implements ListenableFuture<String> {
+ Throwable failure;
+
+ FailFuture(Throwable throwable) {
+ failure = throwable;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ throw new AssertionFailedError("cancel shouldn't be called on this object");
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public boolean isDone() {
+ return true;
+ }
+
+ @Override
+ public String get() throws InterruptedException, ExecutionException {
+ throw new AssertionFailedError("get() shouldn't be called on this object");
+ }
+
+ @Override
+ public String get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return get();
+ }
+
+ @Override
+ protected Throwable tryInternalFastPathGetFailure() {
+ return failure;
+ }
+
+ @Override
+ public void addListener(Runnable listener, Executor executor) {
+ throw new AssertionFailedError("addListener() shouldn't be called on this object");
+ }
+ }
+
+ final RuntimeException exception = new RuntimeException("you still didn't say the magic word!");
+ SettableFuture<String> normalFuture = SettableFuture.create();
+ normalFuture.setFuture(new FailFuture(exception));
+ assertTrue(normalFuture.isDone());
+ try {
+ normalFuture.get();
+ fail();
+ } catch (ExecutionException e) {
+ assertSame(exception, e.getCause());
+ }
+ }
+
private static void awaitUnchecked(final CyclicBarrier barrier) {
try {
barrier.await();
diff --git a/guava/pom.xml b/guava/pom.xml
index a637348..ec99618 100644
--- a/guava/pom.xml
+++ b/guava/pom.xml
@@ -17,6 +17,16 @@
</description>
<dependencies>
<dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>failureaccess</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>listenablefuture</artifactId>
+ <version>9999.0-empty-to-avoid-conflict-with-guava</version>
+ </dependency>
+ <dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
diff --git a/guava/src/com/google/common/util/concurrent/AbstractFuture.java b/guava/src/com/google/common/util/concurrent/AbstractFuture.java
index 803ae2b..c867dbf 100644
--- a/guava/src/com/google/common/util/concurrent/AbstractFuture.java
+++ b/guava/src/com/google/common/util/concurrent/AbstractFuture.java
@@ -20,6 +20,8 @@
import com.google.common.annotations.Beta;
import com.google.common.annotations.GwtCompatible;
+import com.google.common.util.concurrent.internal.InternalFutureFailureAccess;
+import com.google.common.util.concurrent.internal.InternalFutures;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.ForOverride;
import com.google.j2objc.annotations.ReflectionSupport;
@@ -62,7 +64,8 @@
@SuppressWarnings("ShortCircuitBoolean") // we use non-short circuiting comparisons intentionally
@GwtCompatible(emulated = true)
@ReflectionSupport(value = ReflectionSupport.Level.FULL)
-public abstract class AbstractFuture<V> implements ListenableFuture<V> {
+public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
+ implements ListenableFuture<V> {
// NOTE: Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, ||
private static final boolean GENERATE_CANCELLATION_CAUSES =
@@ -847,6 +850,13 @@
}
return v;
}
+ if (future instanceof InternalFutureFailureAccess) {
+ Throwable throwable =
+ InternalFutures.tryInternalFastPathGetFailure((InternalFutureFailureAccess) future);
+ if (throwable != null) {
+ return new Failure(throwable);
+ }
+ }
boolean wasCancelled = future.isCancelled();
// Don't allocate a CancellationException if it's not necessary
if (!GENERATE_CANCELLATION_CAUSES & wasCancelled) {
@@ -967,6 +977,39 @@
@ForOverride
protected void afterDone() {}
+ // TODO(b/114236866): Inherit doc from InternalFutureFailureAccess. Also, -link to its URL.
+ /**
+ * Usually returns {@code null} but, if this {@code Future} has failed, may <i>optionally</i>
+ * return the cause of the failure. "Failure" means specifically "completed with an exception"; it
+ * does not include "was cancelled." To be explicit: If this method returns a non-null value,
+ * then:
+ *
+ * <ul>
+ * <li>{@code isDone()} must return {@code true}
+ * <li>{@code isCancelled()} must return {@code false}
+ * <li>{@code get()} must not block, and it must throw an {@code ExecutionException} with the
+ * return value of this method as its cause
+ * </ul>
+ *
+ * <p>This method is {@code protected} so that classes like {@code
+ * com.google.common.util.concurrent.SettableFuture} do not expose it to their users as an
+ * instance method. In the unlikely event that you need to call this method, call {@link
+ * InternalFutures#tryInternalFastPathGetFailure(InternalFutureFailureAccess)}.
+ *
+ * @since 27.0
+ */
+ @Override
+ @Nullable
+ protected final Throwable tryInternalFastPathGetFailure() {
+ if (this instanceof Trusted) {
+ Object obj = value;
+ if (obj instanceof Failure) {
+ return ((Failure) obj).exception;
+ }
+ }
+ return null;
+ }
+
/**
* Returns the exception that this {@code Future} completed with. This includes completion through
* a call to {@link #setException} or {@link #setFuture setFuture}{@code (failedFuture)} but not
diff --git a/guava/src/com/google/common/util/concurrent/ListenableFuture.java b/guava/src/com/google/common/util/concurrent/ListenableFuture.java
index 5c56973..33b56d6 100644
--- a/guava/src/com/google/common/util/concurrent/ListenableFuture.java
+++ b/guava/src/com/google/common/util/concurrent/ListenableFuture.java
@@ -14,7 +14,6 @@
package com.google.common.util.concurrent;
-import com.google.common.annotations.GwtCompatible;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
@@ -29,6 +28,8 @@
* href="https://github.com/google/guava/wiki/ListenableFutureExplained">{@code
* ListenableFuture}</a>.
*
+ * <p>This class is GWT-compatible.
+ *
* <h3>Purpose</h3>
*
* <p>The main purpose of {@code ListenableFuture} is to help you chain together a graph of
@@ -97,7 +98,6 @@
* @author Nishant Thakkar
* @since 1.0
*/
-@GwtCompatible
public interface ListenableFuture<V> extends Future<V> {
/**
* Registers a listener to be {@linkplain Executor#execute(Runnable) run} on the given executor.