diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java index 10e54b032..65d592a87 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java @@ -76,6 +76,11 @@ public abstract class Task { // last exception private Exception exception; + /** + * When task has been cancelled, task.exception will be null. + * + * @return the exception thrown during execution, possibly from dependents or dependencies. + */ public final Exception getException() { return exception; } 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 d439eac9f..08d5325f6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java @@ -80,8 +80,10 @@ public final class TaskExecutor { return future.get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - } catch (ExecutionException | CancellationException e) { - e.printStackTrace(); + } catch (ExecutionException ignore) { + // We have dealt with ExecutionException in exception handling and uncaught exception handler. + } catch (CancellationException e) { + Logging.LOG.log(Level.INFO, "Task " + firstTask + " has been cancelled.", e); } return false; } @@ -202,8 +204,6 @@ public final class TaskExecutor { } 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; 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 b432dc1f1..ac1b611f6 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/TaskTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/TaskTest.java @@ -3,10 +3,14 @@ package org.jackhuang.hmcl.util; import javafx.embed.swing.JFXPanel; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.util.platform.JavaVersion; import org.junit.Assert; +import org.junit.Assume; import org.junit.Test; +import java.util.concurrent.CancellationException; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -68,4 +72,36 @@ public class TaskTest { Assert.assertTrue("Task does not succeed", result); Assert.assertTrue("ThenAccept has not been executed", flag.get()); } + + @Test + public void testCancellation() { + Task task = Task.runAsync(() -> Thread.sleep(200)); + TaskExecutor executor = task.executor(); + Lang.thread(() -> { + try { + Thread.sleep(100); + executor.cancel(); + } catch (InterruptedException e) { + Assume.assumeNoException(e); + } + }); + Assert.assertFalse("Task should fail because we have cancelled it", executor.test()); + Assert.assertNull("CancellationException should not be recorded.", executor.getException()); + Assert.assertNull("CancellationException should not be recorded.", task.getException()); + } + + @Test + public void testRejectedExecutionException() { + Schedulers.defaultScheduler(); + Schedulers.shutdown(); + + Task task = Task.runAsync(() -> { + Thread.sleep(1000); + }); + + boolean result = task.test(); + + Assert.assertFalse("Task should fail since ExecutorService is shut down and RejectedExecutionException should be thrown", result); + Assert.assertTrue("RejectedExecutionException should be recorded", task.getException() instanceof RejectedExecutionException); + } } \ No newline at end of file