Skip to content

Commit

Permalink
Add toString() to some Future subclasses.
Browse files Browse the repository at this point in the history
I opted for CANCELLED rather than CANCELED to match isCancelled().

Example toString:
com.google.common.util.concurrent.AbstractFutureTest$11@1ae369b7[status=PENDING, info=[setFuture=[com.google.common.util.concurrent.AbstractFutureTest$10@6fffcba5[status=PENDING, info=[cause=[Someday...]]]]]]

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=158789362
  • Loading branch information
clm authored and cpovirk committed Jun 13, 2017
1 parent d8de12d commit 304c634
Show file tree
Hide file tree
Showing 17 changed files with 470 additions and 36 deletions.
Expand Up @@ -198,6 +198,50 @@ public void testRemoveWaiter_polling() throws Exception {
poller.join();
}

public void testToString() throws Exception {
// Two futures should not have the same toString, to avoid people asserting on it
assertThat(SettableFuture.create().toString()).isNotEqualTo(SettableFuture.create().toString());

AbstractFuture<Object> testFuture =
new AbstractFuture<Object>() {
@Override
public String pendingToString() {
return "cause=[Because this test isn't done]";
}
};
assertThat(testFuture.toString())
.contains("status=PENDING, info=[cause=[Because this test isn't done]]");
try {
testFuture.get(1, TimeUnit.NANOSECONDS);
fail();
} catch (TimeoutException e) {
assertThat(e.getMessage()).contains("1 nanoseconds");
assertThat(e.getMessage()).contains("Because this test isn't done");
}
AbstractFuture<Object> testFuture2 =
new AbstractFuture<Object>() {
@Override
public String pendingToString() {
return "cause=[Someday...]";
}
};
AbstractFuture<Object> testFuture3 = new AbstractFuture<Object>() {};
testFuture3.setFuture(testFuture2);
assertThat(testFuture3.toString()).contains("status=PENDING, info=[setFuture=[");
assertThat(testFuture3.toString()).contains("Someday...");
testFuture2.set("result string");
assertThat(testFuture3.toString()).contains("status=SUCCESS, result=[result string]");

assertThat(
new AbstractFuture<Object>() {
@Override
public String pendingToString() {
throw new RuntimeException("I'm a misbehaving implementation");
}
}.toString())
.contains("PENDING");
}

public void testCompletionFinishesWithDone() {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 50000; i++) {
Expand Down
Expand Up @@ -138,6 +138,23 @@ public final void run() {
setResult(fallbackResult);
}

@Override
protected String pendingToString() {
ListenableFuture<? extends V> localInputFuture = inputFuture;
Class<X> localExceptionType = exceptionType;
F localFallback = fallback;
if (localInputFuture != null && localExceptionType != null && localFallback != null) {
return "input=["
+ localInputFuture
+ "], exceptionType=["
+ localExceptionType
+ "], fallback=["
+ localFallback
+ "]";
}
return null;
}

/** Template method for subtypes to actually run the fallback. */
@ForOverride
@Nullable
Expand Down
Expand Up @@ -15,13 +15,15 @@
package com.google.common.util.concurrent;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.base.Throwables.throwIfUnchecked;
import static com.google.common.util.concurrent.Futures.getDone;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;

import com.google.common.annotations.Beta;
import com.google.common.annotations.GwtCompatible;
import com.google.common.base.Ascii;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.DoNotMock;
import com.google.j2objc.annotations.ReflectionSupport;
Expand Down Expand Up @@ -425,7 +427,8 @@ public V get(long timeout, TimeUnit unit)
}
remainingNanos = endNanos - System.nanoTime();
}
throw new TimeoutException();
throw new TimeoutException(
"Waited " + timeout + " " + Ascii.toLowerCase(unit.toString()) + " for " + toString());
}

/**
Expand Down Expand Up @@ -887,6 +890,62 @@ private Listener clearListeners(Listener onto) {
return reversedList;
}

// TODO(user) move this up into FluentFuture, or parts as a default method on ListenableFuture?
@Override
public String toString() {
StringBuilder builder = new StringBuilder().append(super.toString()).append("[status=");
if (isCancelled()) {
builder.append("CANCELLED");
} else if (isDone()) {
addDoneString(builder);
} else {
String pendingDescription;
try {
pendingDescription = pendingToString();
} catch (RuntimeException e) {
// Don't call getMessage or toString() on the exception, in case the exception thrown by the
// subclass is implemented with bugs similar to the subclass.
pendingDescription = "Exception thrown from implementation: " + e.getClass();
}
// The future may complete during or before the call to getPendingToString, so we use null
// as a signal that we should try checking if the future is done again.
if (!isNullOrEmpty(pendingDescription)) {
builder.append("PENDING, info=[").append(pendingDescription).append("]");
} else if (isDone()) {
addDoneString(builder);
} else {
builder.append("PENDING");
}
}
return builder.append("]").toString();
}

/**
* Provide a human-readable explanation of why this future has not yet completed.
*
* @return null if an explanation cannot be provided because the future is done.
*/
@Nullable
String pendingToString() {
Object localValue = value;
if (localValue instanceof SetFuture) {
return "setFuture=[" + ((SetFuture) localValue).future + "]";
}
return null;
}

private void addDoneString(StringBuilder builder) {
try {
builder.append("SUCCESS, result=[").append(getDone(this)).append("]");
} catch (ExecutionException e) {
builder.append("FAILURE, cause=[").append(e.getCause()).append("]");
} catch (CancellationException e) {
builder.append("CANCELLED");
} catch (RuntimeException e) {
builder.append("UNKNOWN, cause=[").append(e.getClass()).append(" thrown from get()]");
}
}

/**
* Submits the given runnable to the given {@link Executor} catching and logging all
* {@linkplain RuntimeException runtime exceptions} thrown by the executor.
Expand Down
Expand Up @@ -193,6 +193,16 @@ protected final void afterDone() {
this.function = null;
}

@Override
protected String pendingToString() {
ListenableFuture<? extends I> localInputFuture = inputFuture;
F localFunction = function;
if (localInputFuture != null && localFunction != null) {
return "inputFuture=[" + localInputFuture + "], function=[" + localFunction + "]";
}
return null;
}

/**
* An {@link AbstractTransformFuture} that delegates to an {@link AsyncFunction} and
* {@link #setFuture(ListenableFuture)}.
Expand Down
Expand Up @@ -67,6 +67,20 @@ protected final void afterDone() {
}
}

@Override
protected String pendingToString() {
RunningState localRunningState = runningState;
if (localRunningState == null) {
return null;
}
ImmutableCollection<? extends ListenableFuture<? extends InputT>> localFutures =
localRunningState.futures;
if (localFutures != null) {
return "futures=[" + localFutures + "]";
}
return null;
}

/**
* Must be called at the end of each sub-class's constructor.
*/
Expand Down
62 changes: 47 additions & 15 deletions android/guava/src/com/google/common/util/concurrent/Futures.java
Expand Up @@ -983,25 +983,42 @@ public static <V> ListenableFuture<V> nonCancellationPropagating(ListenableFutur
if (future.isDone()) {
return future;
}
return new NonCancellationPropagatingFuture<V>(future);
NonCancellationPropagatingFuture<V> output = new NonCancellationPropagatingFuture<V>(future);
future.addListener(output, directExecutor());
return output;
}

/**
* A wrapped future that does not propagate cancellation to its delegate.
*/
/** A wrapped future that does not propagate cancellation to its delegate. */
private static final class NonCancellationPropagatingFuture<V>
extends AbstractFuture.TrustedFuture<V> {
extends AbstractFuture.TrustedFuture<V> implements Runnable {
private ListenableFuture<V> delegate;

NonCancellationPropagatingFuture(final ListenableFuture<V> delegate) {
delegate.addListener(
new Runnable() {
@Override
public void run() {
// This prevents cancellation from propagating because we don't assign delegate until
// delegate is already done, so calling cancel() on it is a no-op.
setFuture(delegate);
}
},
directExecutor());
this.delegate = delegate;
}

@Override
public void run() {
// This prevents cancellation from propagating because we don't assign delegate until
// delegate is already done, so calling cancel() on it is a no-op.
ListenableFuture<V> localDelegate = delegate;
if (localDelegate != null) {
setFuture(localDelegate);
}
}

@Override
protected String pendingToString() {
ListenableFuture<V> localDelegate = delegate;
if (localDelegate != null) {
return "delegate=[" + localDelegate + "]";
}
return null;
}

@Override
protected void afterDone() {
delegate = null;
}
}

Expand Down Expand Up @@ -1127,6 +1144,21 @@ public boolean cancel(boolean interruptIfRunning) {
public void afterDone() {
state = null;
}

@Override
protected String pendingToString() {
InCompletionOrderState<T> localState = state;
if (localState != null) {
// Don't print the actual array! We don't want inCompletionOrder(list).toString() to have
// quadratic output.
return "inputCount=["
+ localState.inputFutures.length
+ "], remaining=["
+ localState.incompleteOutputCount.get()
+ "]";
}
return null;
}
}

private static final class InCompletionOrderState<T> {
Expand Down
Expand Up @@ -125,6 +125,15 @@ public void run() {
}
}

@Override
protected String pendingToString() {
ListenableFuture<? extends V> localInputFuture = delegateRef;
if (localInputFuture != null) {
return "inputFuture=[" + localInputFuture + "]";
}
return null;
}

@Override
protected void afterDone() {
maybePropagateCancellation(delegateRef);
Expand Down
Expand Up @@ -91,8 +91,12 @@ protected void afterDone() {
}

@Override
public String toString() {
return super.toString() + " (delegate = " + task + ")";
protected String pendingToString() {
TrustedFutureInterruptibleTask localTask = task;
if (localTask != null) {
return "task=[" + localTask + "]";
}
return null;
}

@WeakOuter
Expand Down
Expand Up @@ -18,6 +18,7 @@

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.util.concurrent.Futures.getDone;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;

Expand Down Expand Up @@ -197,6 +198,61 @@ final void maybePropagateCancellation(@Nullable Future<?> related) {
}
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder().append(super.toString()).append("[status=");
if (isCancelled()) {
builder.append("CANCELLED");
} else if (isDone()) {
addDoneString(builder);
} else {
String pendingDescription;
try {
pendingDescription = pendingToString();
} catch (RuntimeException e) {
// Don't call getMessage or toString() on the exception, in case the exception thrown by the
// subclass is implemented with bugs similar to the subclass.
pendingDescription = "Exception thrown from implementation: " + e.getClass();
}
// The future may complete during or before the call to getPendingToString, so we use null
// as a signal that we should try checking if the future is done again.
if (!isNullOrEmpty(pendingDescription)) {
builder.append("PENDING, info=[").append(pendingDescription).append("]");
} else if (isDone()) {
addDoneString(builder);
} else {
builder.append("PENDING");
}
}
return builder.append("]").toString();
}

/**
* Provide a human-readable explanation of why this future has not yet completed.
*
* @return null if an explanation cannot be provided because the future is done.
*/
@Nullable
String pendingToString() {
Object localValue = value;
if (localValue instanceof AbstractFuture.SetFuture) {
return "setFuture=[" + ((AbstractFuture.SetFuture) localValue).delegate + "]";
}
return null;
}

private void addDoneString(StringBuilder builder) {
try {
builder.append("SUCCESS, result=[").append(getDone(this)).append("]");
} catch (ExecutionException e) {
builder.append("FAILURE, cause=[").append(e.getCause()).append("]");
} catch (CancellationException e) {
builder.append("CANCELLED");
} catch (RuntimeException e) {
builder.append("UNKNOWN, cause=[").append(e.getClass()).append(" thrown from get()]");
}
}

private enum State {
PENDING {
@Override
Expand Down

0 comments on commit 304c634

Please sign in to comment.