diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java index 64aece34a..ce7392101 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java @@ -58,7 +58,7 @@ public final class TaskExecutor { return success; }) .exceptionally(e -> { - Lang.handleUncaughtException(e instanceof UncheckedThrowable ? e.getCause() : e); + Lang.handleUncaughtException(resolveException(e)); return false; }); return this; @@ -101,11 +101,15 @@ public final class TaskExecutor { }) .thenApplyAsync(unused -> (Exception) null) .exceptionally(throwable -> { - if (throwable instanceof Exception) { - return (Exception) throwable; + if (!(throwable instanceof CompletionException)) + throw new AssertionError(); + + Throwable resolved = resolveException(throwable); + if (resolved instanceof Exception) { + return (Exception) resolved; } else { // If an error occurred, we just rethrow it. - throw new UncheckedThrowable(throwable); + throw (CompletionException) throwable; } }); } @@ -117,20 +121,15 @@ public final class TaskExecutor { private static void scheduleTo(Scheduler scheduler, ExceptionalRunnable runnable, Runnable finalize) { try { scheduler.schedule(runnable).get(); - } catch (ExecutionException e) { - if (e.getCause() instanceof Exception) - rethrow(e.getCause()); - else - throw new UncheckedException(e); - } catch (InterruptedException e) { - throw new UncheckedException(e); + } catch (ExecutionException | InterruptedException e) { + throw new CompletionException(e); } finally { if (finalize != null) finalize.run(); } } - private CompletableFuture executeTask(Task task) { + private CompletableFuture executeTask(Task task) { return CompletableFuture.completedFuture(null).thenComposeAsync(unused -> { task.setState(Task.TaskState.READY); @@ -188,33 +187,36 @@ public final class TaskExecutor { task.setState(Task.TaskState.SUCCEEDED); return null; - }).exceptionally(throwable -> { - if (!(throwable instanceof Exception)) - throw new UncheckedThrowable(throwable); - Exception e = throwable instanceof UncheckedException ? (Exception) throwable.getCause() : (Exception) throwable; - if (e instanceof InterruptedException) { - task.setException(e); - if (task.getSignificance().shouldLog()) { - Logging.LOG.log(Level.FINE, "Task aborted: " + task.getName()); - } - task.onDone().fireEvent(new TaskEvent(this, task, true)); - taskListeners.forEach(it -> it.onFailed(task, e)); - } else if (e instanceof CancellationException || e instanceof RejectedExecutionException) { - if (task.getException() == null) + }).thenApplyAsync(unused -> null).exceptionally(throwable -> { + if (!(throwable instanceof CompletionException)) + throw new AssertionError(); + + Throwable resolved = resolveException(throwable); + if (resolved instanceof Exception) { + Exception e = (Exception) resolved; + if (e instanceof InterruptedException) { task.setException(e); - } else { - task.setException(e); - exception = e; - if (task.getSignificance().shouldLog()) { - Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e); + if (task.getSignificance().shouldLog()) { + Logging.LOG.log(Level.FINE, "Task aborted: " + task.getName()); + } + task.onDone().fireEvent(new TaskEvent(this, task, true)); + taskListeners.forEach(it -> it.onFailed(task, e)); + } else if (e instanceof CancellationException || e instanceof RejectedExecutionException) { + task.setException(e); + } else { + task.setException(e); + exception = e; + if (task.getSignificance().shouldLog()) { + Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e); + } + task.onDone().fireEvent(new TaskEvent(this, task, true)); + taskListeners.forEach(it -> it.onFailed(task, e)); } - task.onDone().fireEvent(new TaskEvent(this, task, true)); - taskListeners.forEach(it -> it.onFailed(task, e)); + + task.setState(Task.TaskState.FAILED); } - task.setState(Task.TaskState.FAILED); - - throw new UncheckedException(e); + throw (CompletionException) throwable; // rethrow error }); } @@ -222,27 +224,20 @@ public final class TaskExecutor { return totTask.get(); } - private static class UncheckedException extends RuntimeException { - - UncheckedException(Exception exception) { - super(exception); - } - } - - private static class UncheckedThrowable extends RuntimeException { - - UncheckedThrowable(Throwable throwable) { - super(throwable); - } + private static Throwable resolveException(Throwable e) { + if (e instanceof ExecutionException || e instanceof CompletionException) + return resolveException(e.getCause()); + else + return e; } private static void rethrow(Throwable e) { - if (e instanceof RuntimeException) { // including UncheckedException and UncheckedThrowable + if (e instanceof ExecutionException || e instanceof CompletionException) { // including UncheckedException and UncheckedThrowable + rethrow(e.getCause()); + } else if (e instanceof RuntimeException) { throw (RuntimeException) e; - } else if (e instanceof Exception) { - throw new UncheckedException((Exception) e); } else { - throw new UncheckedThrowable(e); + throw new CompletionException(e); } } } diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/TaskTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/TaskTest.java index 1704f9c14..edeba03df 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/TaskTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/TaskTest.java @@ -4,15 +4,48 @@ import org.jackhuang.hmcl.task.Task; import org.junit.Assert; import org.junit.Test; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + public class TaskTest { + /** + * TaskExecutor will not catch error and will be thrown to global handler. + */ + @Test + public void expectErrorUncaught() { + AtomicReference throwable = new AtomicReference<>(); + Thread.setDefaultUncaughtExceptionHandler((t, e) -> throwable.set(e)); + Assert.assertFalse(Task.composeAsync(() -> Task.allOf( + Task.allOf(Task.runAsync(() -> { + throw new Error(); + })) + )).whenComplete(Assert::assertNull).test()); + + Assert.assertTrue(throwable.get() instanceof Error); + } + + /** + * + */ @Test public void testWhenComplete() { - Task.composeAsync(() -> Task.allOf( - Task.allOf(Task.runAsync(() -> { - throw new Exception(); - })) - )).whenComplete((exception -> { - Assert.assertFalse(exception == null); - })).test(); + Assert.assertFalse(Task.supplyAsync(() -> { + throw new IllegalStateException(); + }).whenComplete(exception -> { + Assert.assertTrue(exception instanceof IllegalStateException); + }).test()); } -} + + @Test + public void testWithCompose() { + AtomicBoolean bool = new AtomicBoolean(); + boolean success = Task.supplyAsync(() -> { + throw new IllegalStateException(); + }).withRun(() -> { + bool.set(true); + }).test(); + + Assert.assertTrue(success); + Assert.assertTrue(bool.get()); + } +} \ No newline at end of file