After I have switched from Java 11 to Java 17 (OpenJDK installed from Ubuntu 20.04 repository), the following code doesn't work:
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.exception.ExceptionUtils;
public class TestClass {
public static void main(String[] args) {
CompletableFuture<String> streamFuture = CompletableFuture.supplyAsync(() -> {
throw MyException.wrapIfNeeded(new Exception("MyException"));
});
String result = null;
try {
result = streamFuture.get();
} catch (Exception e) {
System.out.println("Exception: " ExceptionUtils.getMessage(e));
}
System.out.println("Result: " Objects.toString(result));
}
static class MyException extends RuntimeException {
private static final long serialVersionUID = 3349188601484197015L;
public MyException(Throwable cause) {
super(cause == null ? null : cause.getMessage(), cause);
}
public static MyException wrapIfNeeded(Throwable e) {
return e instanceof MyException ? (MyException) e : new MyException(e);
}
@Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
}
}
There is a problem in streamFuture.get()
- it hangs infinitely. I dug deeper and found that in java.util.concurrent.ForkJoinPool
there is a method unmanagedBlock(ManagedBlocker blocker)
which looks like
/** ManagedBlock for external threads */
private static void unmanagedBlock(ManagedBlocker blocker)
throws InterruptedException {
if (blocker == null) throw new NullPointerException();
do {} while (!blocker.isReleasable() && !blocker.block());
}
and the program hangs infinitely in the do-while loop.
EDIT:
I found out the problem is cause by the toString()
method added to my custom exception class. For some reason it started to be a problem after Java 11
CodePudding user response:
The problem is due to ReflectionToStringBuilder.toString
use encapsulated java API.
We can try something like MyException.wrapIfNeeded(new NullPointerException("")).toString()
And will see
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field static final long java.lang.RuntimeException.serialVersionUID accessible: module java.base does not "opens java.lang" to unnamed module @b4c966a
Why will this cause hang infinitely?
In CompletableFuture.AsyncSupply#run
method
static final class AsyncSupply<T> extends ForkJoinTask<Void>
implements Runnable, AsynchronousCompletionTask {
...
public void run() {
CompletableFuture<T> d; Supplier<? extends T> f;
if ((d = dep) != null && (f = fn) != null) {
dep = null; fn = null;
if (d.result == null) {
try {
d.completeValue(f.get());
} catch (Throwable ex) {
d.completeThrowable(ex);
}
}
d.postComplete();
}
}
Since MyException
is thrown, d.completeThrowable(ex);
is called, inside this method, it will wrap the exception to CompletionException
, which will call the constructor of Throwable
with MyException
as cause
.
public Throwable(Throwable cause) {
fillInStackTrace();
detailMessage = (cause==null ? null : cause.toString());
this.cause = cause;
}
When cause.toString()
is called, it will throw the exception mentioned in the beginning,
so d.completeThrowable(ex);
is not executed completely and hence the program will hang forever.
CodePudding user response:
I found out the problem is cause by the toString()
method added to my custom exception class. For some reason it (or to be more precise: ReflectionToStringBuilder.toString(this);
) started to be a problem after Java 11.