8312436: CompletableFuture never completes when 'Throwable.toString()' method throws Exception

Reviewed-by: alanb
This commit is contained in:
Viktor Klang 2024-06-05 14:40:04 +00:00
parent 9a8096feb8
commit 326dbb1b13
2 changed files with 61 additions and 11 deletions
src/java.base/share/classes/java/util/concurrent
test/jdk/java/util/concurrent/tck

@ -306,13 +306,57 @@ public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
return RESULT.compareAndSet(this, null, (t == null) ? NIL : t);
}
static CompletionException wrapInCompletionException(Throwable t) {
if (t == null)
return new CompletionException();
String message;
Throwable suppressed;
try {
message = t.toString();
suppressed = null;
} catch (Throwable unknown) {
message = "";
suppressed = unknown;
}
final CompletionException wrapping = new CompletionException(message, t);
if (suppressed != null)
wrapping.addSuppressed(suppressed);
return wrapping;
}
static ExecutionException wrapInExecutionException(Throwable t) {
if (t == null)
return new ExecutionException();
String message;
Throwable suppressed;
try {
message = t.toString();
suppressed = null;
} catch (Throwable unknown) {
message = "";
suppressed = unknown;
}
final ExecutionException wrapping = new ExecutionException(message, t);
if (suppressed != null)
wrapping.addSuppressed(suppressed);
return wrapping;
}
/**
* Returns the encoding of the given (non-null) exception as a
* wrapped CompletionException unless it is one already.
*/
static AltResult encodeThrowable(Throwable x) {
return new AltResult((x instanceof CompletionException) ? x :
new CompletionException(x));
wrapInCompletionException(x));
}
/** Completes with an exceptional result, unless already completed. */
@ -329,7 +373,7 @@ public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
*/
static Object encodeThrowable(Throwable x, Object r) {
if (!(x instanceof CompletionException))
x = new CompletionException(x);
x = wrapInCompletionException(x);
else if (r instanceof AltResult && x == ((AltResult)r).ex)
return r;
return new AltResult(x);
@ -365,7 +409,7 @@ public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
if (r instanceof AltResult
&& (x = ((AltResult)r).ex) != null
&& !(x instanceof CompletionException))
r = new AltResult(new CompletionException(x));
r = new AltResult(wrapInCompletionException(x));
return r;
}
@ -393,7 +437,7 @@ public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
if ((x instanceof CompletionException) &&
(cause = x.getCause()) != null)
x = cause;
throw new ExecutionException(x);
throw wrapInExecutionException(x);
}
return r;
}
@ -410,7 +454,7 @@ public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
throw new CancellationException(details, (CancellationException)x);
if (x instanceof CompletionException)
throw (CompletionException)x;
throw new CompletionException(x);
throw wrapInCompletionException(x);
}
return r;
}
@ -2605,8 +2649,8 @@ public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
/**
* Returns a string identifying this CompletableFuture, as well as
* its completion state. The state, in brackets, contains the
* String {@code "Completed Normally"} or the String {@code
* "Completed Exceptionally"}, or the String {@code "Not
* String {@code "Completed normally"} or the String {@code
* "Completed exceptionally"}, or the String {@code "Not
* completed"} followed by the number of CompletableFutures
* dependent upon its completion, if any.
*
@ -2623,7 +2667,7 @@ public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
? "[Not completed]"
: "[Not completed, " + count + " dependents]")
: (((r instanceof AltResult) && ((AltResult)r).ex != null)
? "[Completed exceptionally: " + ((AltResult)r).ex + "]"
? "[Completed exceptionally: " + ((AltResult)r).ex.getClass().getName() + "]"
: "[Completed normally]"));
}

@ -80,7 +80,14 @@ public class CompletableFutureTest extends JSR166TestCase {
return new TestSuite(CompletableFutureTest.class);
}
static class CFException extends RuntimeException {}
static class CFException extends RuntimeException {
// This makes sure that CompletableFuture still behaves appropriately
// even if thrown exceptions end up throwing exceptions from their String
// representations.
@Override public String getMessage() {
throw new IllegalStateException("malformed");
}
}
void checkIncomplete(CompletableFuture<?> f) {
assertFalse(f.isDone());
@ -272,8 +279,8 @@ public class CompletableFutureTest extends JSR166TestCase {
*/
public void testCompleteExceptionally() {
CompletableFuture<Item> f = new CompletableFuture<>();
CFException ex = new CFException();
checkIncomplete(f);
CFException ex = new CFException();
f.completeExceptionally(ex);
checkCompletedExceptionally(f, ex);
}
@ -5142,5 +5149,4 @@ public class CompletableFutureTest extends JSR166TestCase {
checkCompletedWithWrappedException(g.toCompletableFuture(), r.ex);
r.assertInvoked();
}}
}