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

View file

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

View file

@ -80,7 +80,14 @@ public class CompletableFutureTest extends JSR166TestCase {
return new TestSuite(CompletableFutureTest.class); 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) { void checkIncomplete(CompletableFuture<?> f) {
assertFalse(f.isDone()); assertFalse(f.isDone());
@ -272,8 +279,8 @@ public class CompletableFutureTest extends JSR166TestCase {
*/ */
public void testCompleteExceptionally() { public void testCompleteExceptionally() {
CompletableFuture<Item> f = new CompletableFuture<>(); CompletableFuture<Item> f = new CompletableFuture<>();
CFException ex = new CFException();
checkIncomplete(f); checkIncomplete(f);
CFException ex = new CFException();
f.completeExceptionally(ex); f.completeExceptionally(ex);
checkCompletedExceptionally(f, ex); checkCompletedExceptionally(f, ex);
} }
@ -5142,5 +5149,4 @@ public class CompletableFutureTest extends JSR166TestCase {
checkCompletedWithWrappedException(g.toCompletableFuture(), r.ex); checkCompletedWithWrappedException(g.toCompletableFuture(), r.ex);
r.assertInvoked(); r.assertInvoked();
}} }}
} }