fix(task): wrong cancellation implementation. Closes #1035.

This commit is contained in:
huanghongxun
2021-09-17 17:35:24 +08:00
parent e10e1e0613
commit 390f6e016d
6 changed files with 38 additions and 22 deletions

View File

@@ -57,6 +57,7 @@ import org.jetbrains.annotations.Nullable;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CancellationException;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX; import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap; import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
@@ -166,9 +167,13 @@ public class DownloadPage extends BorderPane implements DecoratorPage {
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest.toFile()); FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest.toFile());
task.setName(file.getName()); task.setName(file.getName());
return task; return task;
}).whenComplete(exception -> { }).whenComplete(Schedulers.javafx(), exception -> {
if (exception != null) { if (exception != null) {
if (exception instanceof CancellationException) {
Controllers.showToast(i18n("message.cancelled"));
} else {
Controllers.dialog(DownloadProviders.localizeErrorMessage(exception), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR); Controllers.dialog(DownloadProviders.localizeErrorMessage(exception), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR);
}
} else { } else {
Controllers.showToast(i18n("install.success")); Controllers.showToast(i18n("install.success"));
} }

View File

@@ -420,6 +420,7 @@ logwindow.export_game_crash_logs=Export game crash info
main_page=Home main_page=Home
message.cancelled=Operation was cancelled
message.confirm=Confirm message.confirm=Confirm
message.copied=Copied to clipboard message.copied=Copied to clipboard
message.doing=Please wait message.doing=Please wait

View File

@@ -420,6 +420,7 @@ logwindow.export_game_crash_logs=導出遊戲崩潰訊息
main_page=首頁 main_page=首頁
message.cancelled=操作被取消
message.confirm=提示 message.confirm=提示
message.copied=已複製到剪貼板 message.copied=已複製到剪貼板
message.doing=請耐心等待 message.doing=請耐心等待

View File

@@ -420,6 +420,7 @@ logwindow.export_game_crash_logs=导出游戏崩溃信息
main_page=主页 main_page=主页
message.cancelled=操作被取消
message.confirm=提示 message.confirm=提示
message.copied=已复制到剪贴板 message.copied=已复制到剪贴板
message.doing=请耐心等待 message.doing=请耐心等待

View File

@@ -95,7 +95,6 @@ public final class AsyncTaskExecutor extends TaskExecutor {
} }
cancelled.set(true); cancelled.set(true);
future.cancel(true);
} }
private CompletableFuture<?> executeTasksExceptionally(Task<?> parentTask, Collection<Task<?>> tasks) { private CompletableFuture<?> executeTasksExceptionally(Task<?> parentTask, Collection<Task<?>> tasks) {
@@ -229,13 +228,15 @@ public final class AsyncTaskExecutor extends TaskExecutor {
.thenComposeAsync(dependentsException -> { .thenComposeAsync(dependentsException -> {
boolean isDependentsSucceeded = dependentsException == null; boolean isDependentsSucceeded = dependentsException == null;
if (!isDependentsSucceeded && task.isRelyingOnDependents()) { if (isDependentsSucceeded) {
task.setDependentsSucceeded();
} else {
task.setException(dependentsException); task.setException(dependentsException);
if (task.isRelyingOnDependents()) {
rethrow(dependentsException); rethrow(dependentsException);
} }
}
if (isDependentsSucceeded)
task.setDependentsSucceeded();
return CompletableFuture.runAsync(wrap(() -> { return CompletableFuture.runAsync(wrap(() -> {
task.setState(Task.TaskState.RUNNING); task.setState(Task.TaskState.RUNNING);
@@ -263,11 +264,13 @@ public final class AsyncTaskExecutor extends TaskExecutor {
.thenApplyAsync(dependenciesException -> { .thenApplyAsync(dependenciesException -> {
boolean isDependenciesSucceeded = dependenciesException == null; boolean isDependenciesSucceeded = dependenciesException == null;
if (!isDependenciesSucceeded && task.isRelyingOnDependencies()) { if (!isDependenciesSucceeded) {
Logging.LOG.severe("Subtasks failed for " + task.getName()); Logging.LOG.severe("Subtasks failed for " + task.getName());
task.setException(dependenciesException); task.setException(dependenciesException);
if (task.isRelyingOnDependencies()) {
rethrow(dependenciesException); rethrow(dependenciesException);
} }
}
checkCancellation(); checkCancellation();
@@ -285,23 +288,20 @@ public final class AsyncTaskExecutor extends TaskExecutor {
.exceptionally(throwable -> { .exceptionally(throwable -> {
Throwable resolved = resolveException(throwable); Throwable resolved = resolveException(throwable);
if (resolved instanceof Exception) { if (resolved instanceof Exception) {
Exception e = (Exception) resolved; Exception e = convertInterruptedException((Exception) resolved);
if (e instanceof InterruptedException || e instanceof CancellationException) { task.setException(e);
task.setException(null); exception = e;
if (e instanceof CancellationException) {
if (task.getSignificance().shouldLog()) { if (task.getSignificance().shouldLog()) {
Logging.LOG.log(Level.FINE, "Task aborted: " + task.getName()); 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 { } else {
task.setException(e);
exception = e;
if (task.getSignificance().shouldLog()) { if (task.getSignificance().shouldLog()) {
Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e); Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e);
} }
}
task.onDone().fireEvent(new TaskEvent(this, task, true)); task.onDone().fireEvent(new TaskEvent(this, task, true));
taskListeners.forEach(it -> it.onFailed(task, e)); taskListeners.forEach(it -> it.onFailed(task, e));
}
task.setState(Task.TaskState.FAILED); task.setState(Task.TaskState.FAILED);
} }
@@ -331,6 +331,14 @@ public final class AsyncTaskExecutor extends TaskExecutor {
} }
} }
private static Exception convertInterruptedException(Exception e) {
if (e instanceof InterruptedException) {
return new CancellationException(e.getMessage());
} else {
return e;
}
}
private static Thread.UncaughtExceptionHandler uncaughtExceptionHandler = null; private static Thread.UncaughtExceptionHandler uncaughtExceptionHandler = null;
public static void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) { public static void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {

View File

@@ -665,14 +665,14 @@ public abstract class Task<T> {
@Override @Override
public void execute() throws Exception { public void execute() throws Exception {
if (isDependentsSucceeded() != (Task.this.getException() == null)) if (isDependentsSucceeded() != (Task.this.getException() == null))
throw new AssertionError("When dependents succeeded, Task.exception must be nonnull."); throw new AssertionError("When whenComplete succeeded, Task.exception must be null.");
action.execute(Task.this.getException()); action.execute(Task.this.getException());
if (!isDependentsSucceeded()) { if (!isDependentsSucceeded()) {
setSignificance(TaskSignificance.MINOR); setSignificance(TaskSignificance.MINOR);
if (Task.this.getException() == null) if (Task.this.getException() == null)
throw new CancellationException(); throw new AssertionError("When failed, exception cannot be null");
else else
throw Task.this.getException(); throw Task.this.getException();
} }