Skip to content

Commit

Permalink
Merge branch 'feat/unwrapCfException-nested' into dev-orl
Browse files Browse the repository at this point in the history
  • Loading branch information
oldratlee committed Feb 11, 2025
2 parents a32280a + 0be43fc commit 2788b09
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 13 deletions.
1 change: 1 addition & 0 deletions .idea/dictionaries/cffu_project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -3925,16 +3925,34 @@ public static <T> CompletableFuture<T>[] completableFutureListToArray(List<Compl

/**
* Unwraps CompletableFuture exception ({@link CompletionException} or {@link ExecutionException})
* to its cause exception. If the input exception is not a CompletableFuture exception or has no cause,
* returns the input exception itself.
* to its cause exception. If the input exception is not a {@code CompletableFuture}/{@code ExecutionException}
* or has no cause or is null, returns the input exception itself.
*
* @param ex the exception to be unwrapped, may be null
* @throws IllegalArgumentException if there is a loop in the causal chain of input
* {@link CompletionException} or {@link ExecutionException}
* @see com.google.common.base.Throwables#getRootCause(Throwable) Guava method Throwables#getRootCause(),
* the loop detection code using fast and slow pointers is adapted from it
*/
@Contract(value = "null -> null; !null -> !null", pure = true)
public static @Nullable Throwable unwrapCfException(@Nullable Throwable ex) {
if (!(ex instanceof CompletionException) && !(ex instanceof ExecutionException)) {
return ex;
public static @Nullable Throwable unwrapCfException(@Nullable final Throwable ex) {
if (ex == null) return null;

// keep a slow pointer that slowly walks the causal chain.
// if the fast pointer ever catches the slower pointer, then there's a loop.
Throwable fastPointer = ex, slowPointer = ex;
boolean advanceSlowPointer = false;

Throwable cause;
while ((cause = fastPointer.getCause()) != null
&& (fastPointer instanceof CompletionException || fastPointer instanceof ExecutionException)) {
fastPointer = cause;
if (fastPointer == slowPointer) throw new IllegalArgumentException("Loop in causal chain detected", ex);

if (advanceSlowPointer) slowPointer = slowPointer.getCause();
advanceSlowPointer = !advanceSlowPointer; // only advance every other iteration
}
if (ex.getCause() == null) return ex;
return ex.getCause();
return fastPointer;
}

private CompletableFutureUtils() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1976,13 +1976,12 @@ void test_completableFutureListToArray() {

@Test
void test_unwrapCfException() {
CompletableFuture<Object> failed = failedFuture(rte);

ExecutionException ee = assertThrowsExactly(ExecutionException.class, () -> failed.get(SHORT_WAIT_MS, MILLISECONDS));
assertSame(rte, unwrapCfException(ee));
RuntimeException rt1 = new RuntimeException();
ExecutionException ee1 = new ExecutionException(rt1);
assertSame(rt1, unwrapCfException(ee1));

CompletionException ce = assertThrowsExactly(CompletionException.class, failed::join);
assertSame(rte, unwrapCfException(ce));
CompletionException ce1 = new CompletionException(rt1);
assertSame(rt1, unwrapCfException(ce1));

CompletionException nakedCe = new CompletionException() {
@java.io.Serial
Expand All @@ -1991,6 +1990,45 @@ void test_unwrapCfException() {
assertSame(nakedCe, unwrapCfException(nakedCe));

assertSame(rte, unwrapCfException(rte));

Throwable ex = new RuntimeException(new RuntimeException(new IllegalArgumentException()));
assertSame(ex, unwrapCfException(ex));
}

@SuppressWarnings("ThrowableNotThrown")
@Test
void test_unwrapCfException_loop() {
{
CompletionException completionException = new CompletionException() {
@java.io.Serial
private static final long serialVersionUID = 0;
};
ExecutionException ee1 = new ExecutionException(completionException);

completionException.initCause(ee1);

assertLoopEx(completionException);
}
{
CompletionException completionException = new CompletionException() {
@java.io.Serial
private static final long serialVersionUID = 0;
};
ExecutionException ee1 = new ExecutionException(completionException);
CompletionException ce2 = new CompletionException(ee1);
ExecutionException ee3 = new ExecutionException(ce2);

completionException.initCause(ee3);

assertLoopEx(completionException);
}
}

private void assertLoopEx(Throwable ex) {
final IllegalArgumentException th = assertThrowsExactly(IllegalArgumentException.class, () -> unwrapCfException(ex));
assertEquals("Loop in causal chain detected", th.getMessage());
assertThrowsExactly(IllegalArgumentException.class, () -> unwrapCfException(new ExecutionException(ex)));
assertThrowsExactly(IllegalArgumentException.class, () -> unwrapCfException(new CompletionException(ex)));
}

// endregion
Expand Down

0 comments on commit 2788b09

Please sign in to comment.