Skip to content

Commit b62d529

Browse files
committed
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
1 parent eb3a9f4 commit b62d529

File tree

19 files changed

+808
-11
lines changed

19 files changed

+808
-11
lines changed

android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureCancellationCauseTest.java

+15-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static com.google.common.truth.Truth.assertThat;
2020

21+
import java.lang.reflect.Method;
2122
import java.net.URLClassLoader;
2223
import java.util.HashMap;
2324
import java.util.Map;
@@ -34,6 +35,8 @@ public class AbstractFutureCancellationCauseTest extends TestCase {
3435

3536
private ClassLoader oldClassLoader;
3637
private URLClassLoader classReloader;
38+
private Class<?> settableFutureClass;
39+
private Class<?> abstractFutureClass;
3740

3841
@Override
3942
protected void setUp() throws Exception {
@@ -68,6 +71,8 @@ public Class<?> loadClass(String name) throws ClassNotFoundException {
6871
};
6972
oldClassLoader = Thread.currentThread().getContextClassLoader();
7073
Thread.currentThread().setContextClassLoader(classReloader);
74+
abstractFutureClass = classReloader.loadClass(AbstractFuture.class.getName());
75+
settableFutureClass = classReloader.loadClass(SettableFuture.class.getName());
7176
}
7277

7378
@Override
@@ -82,6 +87,7 @@ public void testCancel_notDoneNoInterrupt() throws Exception {
8287
assertTrue(future.cancel(false));
8388
assertTrue(future.isCancelled());
8489
assertTrue(future.isDone());
90+
assertNull(tryInternalFastPathGetFailure(future));
8591
try {
8692
future.get();
8793
fail("Expected CancellationException");
@@ -95,6 +101,7 @@ public void testCancel_notDoneInterrupt() throws Exception {
95101
assertTrue(future.cancel(true));
96102
assertTrue(future.isCancelled());
97103
assertTrue(future.isDone());
104+
assertNull(tryInternalFastPathGetFailure(future));
98105
try {
99106
future.get();
100107
fail("Expected CancellationException");
@@ -153,7 +160,13 @@ public void addListener(Runnable runnable, Executor executor) {
153160
}
154161

155162
private Future<?> newFutureInstance() throws Exception {
156-
return (Future<?>)
157-
classReloader.loadClass(SettableFuture.class.getName()).getMethod("create").invoke(null);
163+
return (Future<?>) settableFutureClass.getMethod("create").invoke(null);
164+
}
165+
166+
private Throwable tryInternalFastPathGetFailure(Future<?> future) throws Exception {
167+
Method tryInternalFastPathGetFailureMethod =
168+
abstractFutureClass.getDeclaredMethod("tryInternalFastPathGetFailure");
169+
tryInternalFastPathGetFailureMethod.setAccessible(true);
170+
return (Throwable) tryInternalFastPathGetFailureMethod.invoke(future);
158171
}
159172
}

android/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java

+108
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.common.collect.Iterables;
2323
import com.google.common.collect.Range;
2424
import com.google.common.collect.Sets;
25+
import com.google.common.util.concurrent.internal.InternalFutureFailureAccess;
2526
import java.util.ArrayList;
2627
import java.util.Collections;
2728
import java.util.List;
@@ -971,6 +972,113 @@ public void run() {
971972
t.join();
972973
}
973974

975+
public void testTrustedGetFailure_Completed() {
976+
SettableFuture<String> future = SettableFuture.create();
977+
future.set("261");
978+
assertThat(future.tryInternalFastPathGetFailure()).isNull();
979+
}
980+
981+
public void testTrustedGetFailure_Failed() {
982+
SettableFuture<String> future = SettableFuture.create();
983+
Throwable failure = new Throwable();
984+
future.setException(failure);
985+
assertThat(future.tryInternalFastPathGetFailure()).isEqualTo(failure);
986+
}
987+
988+
public void testTrustedGetFailure_NotCompleted() {
989+
SettableFuture<String> future = SettableFuture.create();
990+
assertThat(future.isDone()).isFalse();
991+
assertThat(future.tryInternalFastPathGetFailure()).isNull();
992+
}
993+
994+
public void testTrustedGetFailure_CanceledNoCause() {
995+
SettableFuture<String> future = SettableFuture.create();
996+
future.cancel(false);
997+
assertThat(future.tryInternalFastPathGetFailure()).isNull();
998+
}
999+
1000+
public void testGetFailure_Completed() {
1001+
AbstractFuture<String> future = new AbstractFuture<String>() {};
1002+
future.set("261");
1003+
assertThat(future.tryInternalFastPathGetFailure()).isNull();
1004+
}
1005+
1006+
public void testGetFailure_Failed() {
1007+
AbstractFuture<String> future = new AbstractFuture<String>() {};
1008+
final Throwable failure = new Throwable();
1009+
future.setException(failure);
1010+
assertThat(future.tryInternalFastPathGetFailure()).isNull();
1011+
}
1012+
1013+
public void testGetFailure_NotCompleted() {
1014+
AbstractFuture<String> future = new AbstractFuture<String>() {};
1015+
assertThat(future.isDone()).isFalse();
1016+
assertThat(future.tryInternalFastPathGetFailure()).isNull();
1017+
}
1018+
1019+
public void testGetFailure_CanceledNoCause() {
1020+
AbstractFuture<String> future = new AbstractFuture<String>() {};
1021+
future.cancel(false);
1022+
assertThat(future.tryInternalFastPathGetFailure()).isNull();
1023+
}
1024+
1025+
public void testForwardExceptionFastPath() throws Exception {
1026+
class FailFuture extends InternalFutureFailureAccess implements ListenableFuture<String> {
1027+
Throwable failure;
1028+
1029+
FailFuture(Throwable throwable) {
1030+
failure = throwable;
1031+
}
1032+
1033+
@Override
1034+
public boolean cancel(boolean mayInterruptIfRunning) {
1035+
throw new AssertionFailedError("cancel shouldn't be called on this object");
1036+
}
1037+
1038+
@Override
1039+
public boolean isCancelled() {
1040+
return false;
1041+
}
1042+
1043+
@Override
1044+
public boolean isDone() {
1045+
return true;
1046+
}
1047+
1048+
@Override
1049+
public String get() throws InterruptedException, ExecutionException {
1050+
throw new AssertionFailedError("get() shouldn't be called on this object");
1051+
}
1052+
1053+
@Override
1054+
public String get(long timeout, TimeUnit unit)
1055+
throws InterruptedException, ExecutionException, TimeoutException {
1056+
return get();
1057+
}
1058+
1059+
@Override
1060+
protected Throwable tryInternalFastPathGetFailure() {
1061+
return failure;
1062+
}
1063+
1064+
@Override
1065+
public void addListener(Runnable listener, Executor executor) {
1066+
throw new AssertionFailedError("addListener() shouldn't be called on this object");
1067+
}
1068+
}
1069+
1070+
final RuntimeException exception = new RuntimeException("you still didn't say the magic word!");
1071+
SettableFuture<String> normalFuture = SettableFuture.create();
1072+
normalFuture.setFuture(new FailFuture(exception));
1073+
assertTrue(normalFuture.isDone());
1074+
try {
1075+
normalFuture.get();
1076+
fail();
1077+
} catch (ExecutionException e) {
1078+
assertSame(exception, e.getCause());
1079+
}
1080+
}
1081+
9741082
private static void awaitUnchecked(final CyclicBarrier barrier) {
9751083
try {
9761084
barrier.await();

android/guava/pom.xml

+10
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@
1616
much more.
1717
</description>
1818
<dependencies>
19+
<dependency>
20+
<groupId>com.google.guava</groupId>
21+
<artifactId>failureaccess</artifactId>
22+
<version>1.0</version>
23+
</dependency>
24+
<dependency>
25+
<groupId>com.google.guava</groupId>
26+
<artifactId>listenablefuture</artifactId>
27+
<version>9999.0-empty-to-avoid-conflict-with-guava</version>
28+
</dependency>
1929
<dependency>
2030
<groupId>com.google.code.findbugs</groupId>
2131
<artifactId>jsr305</artifactId>

android/guava/src/com/google/common/util/concurrent/AbstractFuture.java

+44-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import com.google.common.annotations.Beta;
2222
import com.google.common.annotations.GwtCompatible;
23+
import com.google.common.util.concurrent.internal.InternalFutureFailureAccess;
24+
import com.google.common.util.concurrent.internal.InternalFutures;
2325
import com.google.errorprone.annotations.CanIgnoreReturnValue;
2426
import com.google.errorprone.annotations.ForOverride;
2527
import com.google.j2objc.annotations.ReflectionSupport;
@@ -62,7 +64,8 @@
6264
@SuppressWarnings("ShortCircuitBoolean") // we use non-short circuiting comparisons intentionally
6365
@GwtCompatible(emulated = true)
6466
@ReflectionSupport(value = ReflectionSupport.Level.FULL)
65-
public abstract class AbstractFuture<V> implements ListenableFuture<V> {
67+
public abstract class AbstractFuture<V> extends InternalFutureFailureAccess
68+
implements ListenableFuture<V> {
6669
// NOTE: Whenever both tests are cheap and functional, it's faster to use &, | instead of &&, ||
6770

6871
private static final boolean GENERATE_CANCELLATION_CAUSES =
@@ -847,6 +850,13 @@ private static Object getFutureValue(ListenableFuture<?> future) {
847850
}
848851
return v;
849852
}
853+
if (future instanceof InternalFutureFailureAccess) {
854+
Throwable throwable =
855+
InternalFutures.tryInternalFastPathGetFailure((InternalFutureFailureAccess) future);
856+
if (throwable != null) {
857+
return new Failure(throwable);
858+
}
859+
}
850860
boolean wasCancelled = future.isCancelled();
851861
// Don't allocate a CancellationException if it's not necessary
852862
if (!GENERATE_CANCELLATION_CAUSES & wasCancelled) {
@@ -967,6 +977,39 @@ private static void complete(AbstractFuture<?> future) {
967977
@ForOverride
968978
protected void afterDone() {}
969979

980+
// TODO(b/114236866): Inherit doc from InternalFutureFailureAccess. Also, -link to its URL.
981+
/**
982+
* Usually returns {@code null} but, if this {@code Future} has failed, may <i>optionally</i>
983+
* return the cause of the failure. "Failure" means specifically "completed with an exception"; it
984+
* does not include "was cancelled." To be explicit: If this method returns a non-null value,
985+
* then:
986+
*
987+
* <ul>
988+
* <li>{@code isDone()} must return {@code true}
989+
* <li>{@code isCancelled()} must return {@code false}
990+
* <li>{@code get()} must not block, and it must throw an {@code ExecutionException} with the
991+
* return value of this method as its cause
992+
* </ul>
993+
*
994+
* <p>This method is {@code protected} so that classes like {@code
995+
* com.google.common.util.concurrent.SettableFuture} do not expose it to their users as an
996+
* instance method. In the unlikely event that you need to call this method, call {@link
997+
* InternalFutures#tryInternalFastPathGetFailure(InternalFutureFailureAccess)}.
998+
*
999+
* @since 27.0
1000+
*/
1001+
@Override
1002+
@NullableDecl
1003+
protected final Throwable tryInternalFastPathGetFailure() {
1004+
if (this instanceof Trusted) {
1005+
Object obj = value;
1006+
if (obj instanceof Failure) {
1007+
return ((Failure) obj).exception;
1008+
}
1009+
}
1010+
return null;
1011+
}
1012+
9701013
/**
9711014
* Returns the exception that this {@code Future} completed with. This includes completion through
9721015
* a call to {@link #setException} or {@link #setFuture setFuture}{@code (failedFuture)} but not

android/guava/src/com/google/common/util/concurrent/ListenableFuture.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
package com.google.common.util.concurrent;
1616

17-
import com.google.common.annotations.GwtCompatible;
1817
import java.util.concurrent.Executor;
1918
import java.util.concurrent.Future;
2019
import java.util.concurrent.RejectedExecutionException;
@@ -29,6 +28,8 @@
2928
* href="https://github.com/google/guava/wiki/ListenableFutureExplained">{@code
3029
* ListenableFuture}</a>.
3130
*
31+
* <p>This class is GWT-compatible.
32+
*
3233
* <h3>Purpose</h3>
3334
*
3435
* <p>The main purpose of {@code ListenableFuture} is to help you chain together a graph of
@@ -97,7 +98,6 @@
9798
* @author Nishant Thakkar
9899
* @since 1.0
99100
*/
100-
@GwtCompatible
101101
public interface ListenableFuture<V> extends Future<V> {
102102
/**
103103
* Registers a listener to be {@linkplain Executor#execute(Runnable) run} on the given executor.

futures/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The modules under this directory will be released exactly once each. Once that
2+
happens, there will be no need to ever update any files here.

futures/failureaccess/pom.xml

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>com.google.guava</groupId>
7+
<artifactId>guava-parent</artifactId>
8+
<version>26.0-android</version>
9+
</parent>
10+
<artifactId>failureaccess</artifactId>
11+
<version>1.0</version>
12+
<name>Guava InternalFutureFailureAccess and InternalFutures</name>
13+
<description>
14+
Contains
15+
com.google.common.util.concurrent.internal.InternalFutureFailureAccess and
16+
InternalFutures. Most users will never need to use this artifact. Its
17+
classes is conceptually a part of Guava, but they're in this separate
18+
artifact so that Android libraries can use them without pulling in all of
19+
Guava (just as they can use ListenableFuture by depending on the
20+
listenablefuture artifact).
21+
</description>
22+
<build>
23+
<plugins>
24+
<plugin>
25+
<artifactId>maven-source-plugin</artifactId>
26+
</plugin>
27+
<plugin>
28+
<groupId>org.codehaus.mojo</groupId>
29+
<artifactId>animal-sniffer-maven-plugin</artifactId>
30+
</plugin>
31+
<plugin>
32+
<artifactId>maven-javadoc-plugin</artifactId>
33+
<executions>
34+
<execution>
35+
<id>attach-docs</id>
36+
</execution>
37+
<execution>
38+
<id>generate-javadoc-site-report</id>
39+
<phase>site</phase>
40+
<goals><goal>javadoc</goal></goals>
41+
</execution>
42+
</executions>
43+
</plugin>
44+
</plugins>
45+
</build>
46+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (C) 2018 The Guava Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package com.google.common.util.concurrent.internal;
16+
17+
/**
18+
* A future that, if it fails, may <i>optionally</i> provide access to the cause of the failure.
19+
*
20+
* <p>This class is used only for micro-optimization. Standard {@code Future} utilities benefit from
21+
* this optimization, so there is no need to specialize methods to return or accept this type
22+
* instead of {@code ListenableFuture}.
23+
*
24+
* <p>This class is GWT-compatible.
25+
*
26+
* @since {@code com.google.guava:failureaccess:1.0}, which was added as a dependency of Guava in
27+
* Guava 27.0
28+
*/
29+
public abstract class InternalFutureFailureAccess {
30+
/** Constructor for use by subclasses. */
31+
protected InternalFutureFailureAccess() {}
32+
33+
/**
34+
* Usually returns {@code null} but, if this {@code Future} has failed, may <i>optionally</i>
35+
* return the cause of the failure. "Failure" means specifically "completed with an exception"; it
36+
* does not include "was cancelled." To be explicit: If this method returns a non-null value,
37+
* then:
38+
*
39+
* <ul>
40+
* <li>{@code isDone()} must return {@code true}
41+
* <li>{@code isCancelled()} must return {@code false}
42+
* <li>{@code get()} must not block, and it must throw an {@code ExecutionException} with the
43+
* return value of this method as its cause
44+
* </ul>
45+
*
46+
* <p>This method is {@code protected} so that classes like {@code
47+
* com.google.common.util.concurrent.SettableFuture} do not expose it to their users as an
48+
* instance method. In the unlikely event that you need to call this method, call {@link
49+
* InternalFutures#tryInternalFastPathGetFailure(InternalFutureFailureAccess)}.
50+
*/
51+
protected abstract Throwable tryInternalFastPathGetFailure();
52+
}

0 commit comments

Comments
 (0)