From b62d52926b904a8def90fd427acad53d79fa0e9f Mon Sep 17 00:00:00 2001 From: cpovirk Date: Tue, 11 Sep 2018 14:04:55 -0700 Subject: [PATCH] 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 --- .../AbstractFutureCancellationCauseTest.java | 17 ++- .../util/concurrent/AbstractFutureTest.java | 108 +++++++++++++ android/guava/pom.xml | 10 ++ .../util/concurrent/AbstractFuture.java | 45 +++++- .../util/concurrent/ListenableFuture.java | 4 +- futures/README.md | 2 + futures/failureaccess/pom.xml | 46 ++++++ .../internal/InternalFutureFailureAccess.java | 52 +++++++ .../concurrent/internal/InternalFutures.java | 45 ++++++ futures/listenablefuture1/pom.xml | 56 +++++++ .../util/concurrent/ListenableFuture.java | 144 ++++++++++++++++++ futures/listenablefuture9999/pom.xml | 56 +++++++ guava-gwt/pom.xml | 41 +++++ .../util/concurrent/AbstractFuture.java | 9 +- .../AbstractFutureCancellationCauseTest.java | 17 ++- .../util/concurrent/AbstractFutureTest.java | 108 +++++++++++++ guava/pom.xml | 10 ++ .../util/concurrent/AbstractFuture.java | 45 +++++- .../util/concurrent/ListenableFuture.java | 4 +- 19 files changed, 808 insertions(+), 11 deletions(-) create mode 100644 futures/README.md create mode 100644 futures/failureaccess/pom.xml create mode 100644 futures/failureaccess/src/com/google/common/util/concurrent/internal/InternalFutureFailureAccess.java create mode 100644 futures/failureaccess/src/com/google/common/util/concurrent/internal/InternalFutures.java create mode 100644 futures/listenablefuture1/pom.xml create mode 100644 futures/listenablefuture1/src/com/google/common/util/concurrent/ListenableFuture.java create mode 100644 futures/listenablefuture9999/pom.xml 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 380f69ffcd79..6c921b5b67d5 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 @@ public class AbstractFutureCancellationCauseTest extends TestCase { private ClassLoader oldClassLoader; private URLClassLoader classReloader; + private Class settableFutureClass; + private Class abstractFutureClass; @Override protected void setUp() throws Exception { @@ -68,6 +71,8 @@ public Class loadClass(String name) throws ClassNotFoundException { }; 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 @@ public void testCancel_notDoneNoInterrupt() throws Exception { assertTrue(future.cancel(false)); assertTrue(future.isCancelled()); assertTrue(future.isDone()); + assertNull(tryInternalFastPathGetFailure(future)); try { future.get(); fail("Expected CancellationException"); @@ -95,6 +101,7 @@ public void testCancel_notDoneInterrupt() throws Exception { assertTrue(future.cancel(true)); assertTrue(future.isCancelled()); assertTrue(future.isDone()); + assertNull(tryInternalFastPathGetFailure(future)); try { future.get(); fail("Expected CancellationException"); @@ -153,7 +160,13 @@ public void addListener(Runnable runnable, Executor executor) { } 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 c8500320f284..2bdbef324e42 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 @@ public void run() { t.join(); } + public void testTrustedGetFailure_Completed() { + SettableFuture future = SettableFuture.create(); + future.set("261"); + assertThat(future.tryInternalFastPathGetFailure()).isNull(); + } + + public void testTrustedGetFailure_Failed() { + SettableFuture future = SettableFuture.create(); + Throwable failure = new Throwable(); + future.setException(failure); + assertThat(future.tryInternalFastPathGetFailure()).isEqualTo(failure); + } + + public void testTrustedGetFailure_NotCompleted() { + SettableFuture future = SettableFuture.create(); + assertThat(future.isDone()).isFalse(); + assertThat(future.tryInternalFastPathGetFailure()).isNull(); + } + + public void testTrustedGetFailure_CanceledNoCause() { + SettableFuture future = SettableFuture.create(); + future.cancel(false); + assertThat(future.tryInternalFastPathGetFailure()).isNull(); + } + + public void testGetFailure_Completed() { + AbstractFuture future = new AbstractFuture() {}; + future.set("261"); + assertThat(future.tryInternalFastPathGetFailure()).isNull(); + } + + public void testGetFailure_Failed() { + AbstractFuture future = new AbstractFuture() {}; + final Throwable failure = new Throwable(); + future.setException(failure); + assertThat(future.tryInternalFastPathGetFailure()).isNull(); + } + + public void testGetFailure_NotCompleted() { + AbstractFuture future = new AbstractFuture() {}; + assertThat(future.isDone()).isFalse(); + assertThat(future.tryInternalFastPathGetFailure()).isNull(); + } + + public void testGetFailure_CanceledNoCause() { + AbstractFuture future = new AbstractFuture() {}; + future.cancel(false); + assertThat(future.tryInternalFastPathGetFailure()).isNull(); + } + + public void testForwardExceptionFastPath() throws Exception { + class FailFuture extends InternalFutureFailureAccess implements ListenableFuture { + 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 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 f0148787cc14..f4dcddb48c13 100644 --- a/android/guava/pom.xml +++ b/android/guava/pom.xml @@ -16,6 +16,16 @@ much more. + + com.google.guava + failureaccess + 1.0 + + + com.google.guava + listenablefuture + 9999.0-empty-to-avoid-conflict-with-guava + com.google.code.findbugs jsr305 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 564888b84cf3..740c58d298cf 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 implements ListenableFuture { +public abstract class AbstractFuture extends InternalFutureFailureAccess + implements ListenableFuture { // 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 @@ private static Object getFutureValue(ListenableFuture future) { } 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 @@ private static void complete(AbstractFuture future) { @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 optionally + * 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: + * + *
    + *
  • {@code isDone()} must return {@code true} + *
  • {@code isCancelled()} must return {@code false} + *
  • {@code get()} must not block, and it must throw an {@code ExecutionException} with the + * return value of this method as its cause + *
+ * + *

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 5c569730f19f..33b56d6a1bcd 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}. * + *

This class is GWT-compatible. + * *

Purpose

* *

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 extends Future { /** * 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 000000000000..7430ebdad391 --- /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 000000000000..01ce0922172c --- /dev/null +++ b/futures/failureaccess/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + com.google.guava + guava-parent + 26.0-android + + failureaccess + 1.0 + Guava InternalFutureFailureAccess and InternalFutures + + 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). + + + + + maven-source-plugin + + + org.codehaus.mojo + animal-sniffer-maven-plugin + + + maven-javadoc-plugin + + + attach-docs + + + generate-javadoc-site-report + site + javadoc + + + + + + 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 000000000000..7cc84898bfb8 --- /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 optionally provide access to the cause of the failure. + * + *

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}. + * + *

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 optionally + * 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: + * + *

    + *
  • {@code isDone()} must return {@code true} + *
  • {@code isCancelled()} must return {@code false} + *
  • {@code get()} must not block, and it must throw an {@code ExecutionException} with the + * return value of this method as its cause + *
+ * + *

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 000000000000..42df5ec14588 --- /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. + * + *

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 optionally + * 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: + * + *

    + *
  • {@code isDone()} must return {@code true} + *
  • {@code isCancelled()} must return {@code false} + *
  • {@code get()} must not block, and it must throw an {@code ExecutionException} with the + * return value of this method as its cause + *
+ */ + 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 000000000000..3bd8c01db211 --- /dev/null +++ b/futures/listenablefuture1/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + com.google.guava + guava-parent + 26.0-android + + listenablefuture + 1.0 + Guava ListenableFuture only + + 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-.... + + + + + maven-source-plugin + + + org.codehaus.mojo + animal-sniffer-maven-plugin + + + maven-javadoc-plugin + + + attach-docs + + + generate-javadoc-site-report + site + javadoc + + + + + + 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 000000000000..33b56d6a1bcd --- /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. + * + *

See the Guava User Guide article on {@code + * ListenableFuture}. + * + *

This class is GWT-compatible. + * + *

Purpose

+ * + *

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: + * + *

+ * + *

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: + * + *

{@code
+ * final String name = ...;
+ * inFlight.add(name);
+ * ListenableFuture 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);
+ * }
+ * + *

How to get an instance

+ * + *

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: + * + *

    + *
  • 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}. + *
  • 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}.) + *
+ * + *

Test doubles: 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. Avoid 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. + * + *

Custom implementation: 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}. + * + *

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 extends Future { + /** + * 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. + * + *

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. + * + *

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. + * + *

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: + * + *

    + *
  • 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. + *
  • 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. + *
  • The listener may delay other listeners, even listeners that are not themselves {@code + * directExecutor} listeners. + *
+ * + *

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()}. + * + *

Memory consistency effects: Actions in a thread prior to adding a listener + * happen-before 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 000000000000..ad3f23ec6585 --- /dev/null +++ b/futures/listenablefuture9999/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + com.google.guava + guava-parent + 26.0-android + + listenablefuture + 9999.0-empty-to-avoid-conflict-with-guava + Guava ListenableFuture only + + 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-.... + + + + + maven-source-plugin + + + org.codehaus.mojo + animal-sniffer-maven-plugin + + + maven-javadoc-plugin + + + attach-docs + + + generate-javadoc-site-report + site + javadoc + + + + + + diff --git a/guava-gwt/pom.xml b/guava-gwt/pom.xml index a20c034426fd..5fa6c42e81cd 100644 --- a/guava-gwt/pom.xml +++ b/guava-gwt/pom.xml @@ -44,6 +44,11 @@ + + com.google.guava + failureaccess + 1.0 + com.google.guava guava @@ -124,6 +129,20 @@ maven-dependency-plugin + + unpack-failureaccess-sources + generate-resources + unpack-dependencies + + failureaccess + sources + true + META-INF/MANIFEST.MF + ${project.build.directory}/failureaccess-sources + java-source + false + + unpack-guava-sources generate-resources @@ -182,6 +201,12 @@ + + com.google.guava + failureaccess + 1.0 + sources + com.google.guava guava @@ -222,6 +247,22 @@ + + + + + + + + + + + + + + + + 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 bc05340586da..96691b8a7a70 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 implements ListenableFuture { +public abstract class AbstractFuture extends InternalFutureFailureAccess + implements ListenableFuture { /** * Tag interface marking trusted subclasses. This enables some optimizations. The implementation @@ -215,6 +217,11 @@ private void notifyAndClearListeners() { 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 380f69ffcd79..6c921b5b67d5 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 @@ public class AbstractFutureCancellationCauseTest extends TestCase { private ClassLoader oldClassLoader; private URLClassLoader classReloader; + private Class settableFutureClass; + private Class abstractFutureClass; @Override protected void setUp() throws Exception { @@ -68,6 +71,8 @@ public Class loadClass(String name) throws ClassNotFoundException { }; 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 @@ public void testCancel_notDoneNoInterrupt() throws Exception { assertTrue(future.cancel(false)); assertTrue(future.isCancelled()); assertTrue(future.isDone()); + assertNull(tryInternalFastPathGetFailure(future)); try { future.get(); fail("Expected CancellationException"); @@ -95,6 +101,7 @@ public void testCancel_notDoneInterrupt() throws Exception { assertTrue(future.cancel(true)); assertTrue(future.isCancelled()); assertTrue(future.isDone()); + assertNull(tryInternalFastPathGetFailure(future)); try { future.get(); fail("Expected CancellationException"); @@ -153,7 +160,13 @@ public void addListener(Runnable runnable, Executor executor) { } 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 c8500320f284..2bdbef324e42 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 @@ public void run() { t.join(); } + public void testTrustedGetFailure_Completed() { + SettableFuture future = SettableFuture.create(); + future.set("261"); + assertThat(future.tryInternalFastPathGetFailure()).isNull(); + } + + public void testTrustedGetFailure_Failed() { + SettableFuture future = SettableFuture.create(); + Throwable failure = new Throwable(); + future.setException(failure); + assertThat(future.tryInternalFastPathGetFailure()).isEqualTo(failure); + } + + public void testTrustedGetFailure_NotCompleted() { + SettableFuture future = SettableFuture.create(); + assertThat(future.isDone()).isFalse(); + assertThat(future.tryInternalFastPathGetFailure()).isNull(); + } + + public void testTrustedGetFailure_CanceledNoCause() { + SettableFuture future = SettableFuture.create(); + future.cancel(false); + assertThat(future.tryInternalFastPathGetFailure()).isNull(); + } + + public void testGetFailure_Completed() { + AbstractFuture future = new AbstractFuture() {}; + future.set("261"); + assertThat(future.tryInternalFastPathGetFailure()).isNull(); + } + + public void testGetFailure_Failed() { + AbstractFuture future = new AbstractFuture() {}; + final Throwable failure = new Throwable(); + future.setException(failure); + assertThat(future.tryInternalFastPathGetFailure()).isNull(); + } + + public void testGetFailure_NotCompleted() { + AbstractFuture future = new AbstractFuture() {}; + assertThat(future.isDone()).isFalse(); + assertThat(future.tryInternalFastPathGetFailure()).isNull(); + } + + public void testGetFailure_CanceledNoCause() { + AbstractFuture future = new AbstractFuture() {}; + future.cancel(false); + assertThat(future.tryInternalFastPathGetFailure()).isNull(); + } + + public void testForwardExceptionFastPath() throws Exception { + class FailFuture extends InternalFutureFailureAccess implements ListenableFuture { + 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 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 a637348f718e..ec99618038cf 100644 --- a/guava/pom.xml +++ b/guava/pom.xml @@ -16,6 +16,16 @@ much more. + + com.google.guava + failureaccess + 1.0 + + + com.google.guava + listenablefuture + 9999.0-empty-to-avoid-conflict-with-guava + com.google.code.findbugs jsr305 diff --git a/guava/src/com/google/common/util/concurrent/AbstractFuture.java b/guava/src/com/google/common/util/concurrent/AbstractFuture.java index 803ae2bf09f2..c867dbf5b293 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 implements ListenableFuture { +public abstract class AbstractFuture extends InternalFutureFailureAccess + implements ListenableFuture { // 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 @@ private static Object getFutureValue(ListenableFuture future) { } 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 @@ private static void complete(AbstractFuture future) { @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 optionally + * 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: + * + *

    + *
  • {@code isDone()} must return {@code true} + *
  • {@code isCancelled()} must return {@code false} + *
  • {@code get()} must not block, and it must throw an {@code ExecutionException} with the + * return value of this method as its cause + *
+ * + *

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 5c569730f19f..33b56d6a1bcd 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}. * + *

This class is GWT-compatible. + * *

Purpose

* *

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 extends Future { /** * Registers a listener to be {@linkplain Executor#execute(Runnable) run} on the given executor.