mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-15 23:06:07 -04:00
Merge branch 'javafx' into patch-1
This commit is contained in:
commit
e67a1d16f5
@ -22,7 +22,7 @@ import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.AsyncTaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.CrashReporter;
|
||||
@ -78,7 +78,7 @@ public final class Launcher extends Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Thread.setDefaultUncaughtExceptionHandler(CRASH_REPORTER);
|
||||
TaskExecutor.setUncaughtExceptionHandler(new CrashReporter(false));
|
||||
AsyncTaskExecutor.setUncaughtExceptionHandler(new CrashReporter(false));
|
||||
|
||||
try {
|
||||
LOG.info("*** " + Metadata.TITLE + " ***");
|
||||
|
@ -154,12 +154,11 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
if (id == null || !isLoaded())
|
||||
return newImage("/assets/img/grass.png");
|
||||
|
||||
Version version = getVersion(id);
|
||||
Version version = getVersion(id).resolve(this);
|
||||
File iconFile = getVersionIconFile(id);
|
||||
if (iconFile.exists())
|
||||
return new Image("file:" + iconFile.getAbsolutePath());
|
||||
else if (!version.getPatches().isEmpty() ||
|
||||
version.getMainClass() != null &&
|
||||
else if (version.getMainClass() != null &&
|
||||
("net.minecraft.launchwrapper.Launch".equals(version.getMainClass())
|
||||
|| version.getMainClass().startsWith("net.fabricmc")
|
||||
|| "cpw.mods.modlauncher.Launcher".equals(version.getMainClass())))
|
||||
|
@ -23,7 +23,9 @@ import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.CharacterDeletedException;
|
||||
import org.jackhuang.hmcl.auth.CredentialExpiredException;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
|
||||
@ -55,6 +57,7 @@ import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.ManagedProcess;
|
||||
@ -64,7 +67,14 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@ -203,7 +213,7 @@ public final class LauncherHelper {
|
||||
});
|
||||
}
|
||||
})
|
||||
.executor();
|
||||
.cancellableExecutor();
|
||||
|
||||
launchingStepsPane.setExecutor(executor, false);
|
||||
executor.addTaskListener(new TaskListener() {
|
||||
@ -239,9 +249,32 @@ public final class LauncherHelper {
|
||||
} else if (ex instanceof NotDecompressingNativesException) {
|
||||
message = i18n("launch.failed.decompressing_natives") + ex.getLocalizedMessage();
|
||||
} else if (ex instanceof LibraryDownloadException) {
|
||||
message = i18n("launch.failed.download_library", ((LibraryDownloadException) ex).getLibrary().getName()) + "\n" + StringUtils.getStackTrace(ex.getCause());
|
||||
message = i18n("launch.failed.download_library", ((LibraryDownloadException) ex).getLibrary().getName()) + "\n";
|
||||
if (ex.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException rce = (ResponseCodeException) ex.getCause();
|
||||
int responseCode = rce.getResponseCode();
|
||||
URL url = rce.getUrl();
|
||||
if (responseCode == 404)
|
||||
message += i18n("download.code.404", url);
|
||||
else
|
||||
message += i18n("download.failed", url, responseCode);
|
||||
} else {
|
||||
message += StringUtils.getStackTrace(ex.getCause());
|
||||
}
|
||||
} else if (ex instanceof GameAssetIndexDownloadTask.GameAssetIndexMalformedException) {
|
||||
message = i18n("assets.index.malformed");
|
||||
} else if (ex instanceof AuthlibInjectorDownloadException) {
|
||||
message = i18n("account.failed.injector_download_failure");
|
||||
} else if (ex instanceof CharacterDeletedException) {
|
||||
message = i18n("account.failed.character_deleted");
|
||||
} else if (ex instanceof ResponseCodeException) {
|
||||
ResponseCodeException rce = (ResponseCodeException) ex;
|
||||
int responseCode = rce.getResponseCode();
|
||||
URL url = rce.getUrl();
|
||||
if (responseCode == 404)
|
||||
message = i18n("download.code.404", url);
|
||||
else
|
||||
message = i18n("download.failed", url, responseCode);
|
||||
} else {
|
||||
message = StringUtils.getStackTrace(ex);
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@ -103,7 +104,19 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
|
||||
public static void alertFailureMessage(Exception exception, Runnable next) {
|
||||
if (exception instanceof LibraryDownloadException) {
|
||||
Controllers.dialog(i18n("launch.failed.download_library", ((LibraryDownloadException) exception).getLibrary().getName()) + "\n" + StringUtils.getStackTrace(exception.getCause()), i18n("install.failed.downloading"), MessageType.ERROR, next);
|
||||
String message = i18n("launch.failed.download_library", ((LibraryDownloadException) exception).getLibrary().getName()) + "\n";
|
||||
if (exception.getCause() instanceof ResponseCodeException) {
|
||||
ResponseCodeException rce = (ResponseCodeException) exception.getCause();
|
||||
int responseCode = rce.getResponseCode();
|
||||
URL url = rce.getUrl();
|
||||
if (responseCode == 404)
|
||||
message += i18n("download.code.404", url);
|
||||
else
|
||||
message += i18n("download.failed", url, responseCode);
|
||||
} else {
|
||||
message += StringUtils.getStackTrace(exception.getCause());
|
||||
}
|
||||
Controllers.dialog(message, i18n("install.failed.downloading"), MessageType.ERROR, next);
|
||||
} else if (exception instanceof DownloadException) {
|
||||
if (exception.getCause() instanceof SocketTimeoutException) {
|
||||
Controllers.dialog(i18n("install.failed.downloading.timeout", ((DownloadException) exception).getUrl()), i18n("install.failed.downloading"), MessageType.ERROR, next);
|
||||
|
@ -60,7 +60,7 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
}
|
||||
|
||||
runInFX(() -> {
|
||||
TaskExecutor executor = task.executor(new TaskListener() {
|
||||
TaskExecutor executor = task.cancellableExecutor(new TaskListener() {
|
||||
@Override
|
||||
public void onStop(boolean success, TaskExecutor executor) {
|
||||
runInFX(() -> {
|
||||
|
@ -92,8 +92,8 @@ crash.NoClassDefFound=Please verify that the "Hello Minecraft! Launcher" softwar
|
||||
crash.user_fault=Your OS or Java environment may not be properly installed which may result in a crash, please check your Java Runtime Environment or your computer!
|
||||
|
||||
download=Download
|
||||
download.code.404=File not found on the remote server
|
||||
download.failed=Failed to download
|
||||
download.code.404=File not found on the remote server: %s
|
||||
download.failed=Failed to download %1$s, response code: %2$d
|
||||
download.failed.empty=No candidates. Click here to return.
|
||||
download.failed.refresh=Unable to download version list. Click here to retry.
|
||||
download.provider.mcbbs=MCBBS (https://www.mcbbs.net/)
|
||||
|
@ -91,8 +91,8 @@ crash.NoClassDefFound=Favor verificar que el software "Hello Minecraft! Launcher
|
||||
crash.user_fault=Su SO o ambiente de Java podría estar mal instalado resultando en fallos de este software, ¡por favor verifique su ambiente de Java o computadora!
|
||||
|
||||
download=Descargar
|
||||
download.code.404=Archivo no encontrado en servidor remoto
|
||||
download.failed=Falló en descargar
|
||||
download.code.404=Archivo no encontrado en servidor remoto: %s
|
||||
download.failed=Falló en descargar: %1$s
|
||||
download.failed.empty=No hay candidatos. Clic aquí para regresar.
|
||||
download.failed.refresh=No se pudo cargar lista de versiones. Clic aquí para reintentar.
|
||||
download.provider.mcbbs=MCBBS (https://www.mcbbs.net/)
|
||||
|
@ -91,8 +91,8 @@ crash.NoClassDefFound=Пожалуйста, проверьте, что прог
|
||||
crash.user_fault=Ваша ОС или среда Java могут быть неправильно установлены, что приведет к сбою этого программного обеспечения, пожалуйста, проверьте свою среду Java или свой компьютер\!
|
||||
|
||||
download=Загрузка
|
||||
download.code.404=Файл не найден на удаленном сервере
|
||||
download.failed=Не удалось загрузить
|
||||
download.code.404=Файл не найден на удаленном сервере: %s
|
||||
download.failed=Не удалось загрузить: %1$s
|
||||
download.failed.empty=Нет вариантов. Нажмите здесь, чтобы вернуться.
|
||||
download.failed.refresh=Невозможно загрузить список версий. Нажмите здесь, чтобы повторить попытку.
|
||||
download.provider.mcbbs=MCBBS (https://www.mcbbs.net/)
|
||||
|
@ -91,8 +91,8 @@ crash.NoClassDefFound=請確認 Hello Minecraft! Launcher 本體是否完整,
|
||||
crash.user_fault=您的系統或 Java 環境可能安裝不當導致本軟體當機,請檢查您的 Java 環境或您的電腦! 可以嘗試重新安裝 Java。
|
||||
|
||||
download=下載
|
||||
download.code.404=遠端伺服器沒有需要下載的檔案
|
||||
download.failed=下載失敗
|
||||
download.code.404=遠端伺服器沒有需要下載的檔案: %s
|
||||
download.failed=下載失敗: %1$s,錯誤碼:%2$d
|
||||
download.failed.empty=沒有能安裝的版本,按一下此處返回。
|
||||
download.failed.refresh=載入版本列表失敗,按一下此處重試。
|
||||
download.provider.mcbbs=我的世界中文論壇 (MCBBS, https://www.mcbbs.net/)
|
||||
|
@ -91,8 +91,8 @@ crash.NoClassDefFound=请确认 Hello Minecraft! Launcher 本体是否完整,
|
||||
crash.user_fault=您的系统或 Java 环境可能安装不当导致本软件崩溃,请检查您的 Java 环境或您的电脑!可以尝试重新安装 Java。
|
||||
|
||||
download=下载
|
||||
download.code.404=远程服务器不包含需要下载的文件
|
||||
download.failed=下载失败
|
||||
download.code.404=远程服务器不包含需要下载的文件: %s
|
||||
download.failed=下载失败: %1$s,错误码:%2$d
|
||||
download.failed.empty=没有可供安装的版本,点击此处返回。
|
||||
download.failed.refresh=加载版本列表失败,点击此处重试。
|
||||
download.provider.mcbbs=我的世界中文论坛 (MCBBS, https://www.mcbbs.net/)
|
||||
|
@ -72,7 +72,6 @@ public class MojangDownloadProvider implements DownloadProvider {
|
||||
|
||||
@Override
|
||||
public String injectURL(String baseURL) {
|
||||
return baseURL
|
||||
.replaceFirst("https?://files\\.minecraftforge\\.net/maven", "https://bmclapi2.bangbang93.com/maven");
|
||||
return baseURL;
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import org.jackhuang.hmcl.util.CacheRepository;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -47,8 +48,6 @@ public final class GameAssetDownloadTask extends Task<Void> {
|
||||
private final File assetIndexFile;
|
||||
private final List<Task<?>> dependents = new LinkedList<>();
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
private AssetIndex index;
|
||||
private boolean retry = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@ -62,8 +61,15 @@ public final class GameAssetDownloadTask extends Task<Void> {
|
||||
this.assetIndexInfo = this.version.getAssetIndex();
|
||||
this.assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId());
|
||||
|
||||
if (!assetIndexFile.exists() || forceDownloadingIndex)
|
||||
if (!assetIndexFile.exists() || forceDownloadingIndex) {
|
||||
dependents.add(new GameAssetIndexDownloadTask(dependencyManager, this.version));
|
||||
} else {
|
||||
try {
|
||||
JsonUtils.GSON.fromJson(FileUtils.readText(assetIndexFile), AssetIndex.class);
|
||||
} catch (IOException | JsonSyntaxException e) {
|
||||
dependents.add(new GameAssetIndexDownloadTask(dependencyManager, this.version));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -77,28 +83,12 @@ public final class GameAssetDownloadTask extends Task<Void> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doPreExecute() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preExecute() throws Exception {
|
||||
public void execute() throws Exception {
|
||||
AssetIndex index;
|
||||
try {
|
||||
index = JsonUtils.GSON.fromJson(FileUtils.readText(assetIndexFile), AssetIndex.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
dependents.add(new GameAssetIndexDownloadTask(dependencyManager, this.version));
|
||||
retry = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if (retry) {
|
||||
try {
|
||||
index = JsonUtils.GSON.fromJson(FileUtils.readText(assetIndexFile), AssetIndex.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new GameAssetIndexDownloadTask.GameAssetIndexMalformedException();
|
||||
}
|
||||
} catch (IOException | JsonSyntaxException e) {
|
||||
throw new GameAssetIndexDownloadTask.GameAssetIndexMalformedException();
|
||||
}
|
||||
|
||||
int progress = 0;
|
||||
|
@ -217,9 +217,12 @@ public class Version implements Comparable<Version>, Validation {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve given version
|
||||
* Resolve given version.
|
||||
* Resolving version will list all patches within this version and its parents,
|
||||
* which is for analysis.
|
||||
*/
|
||||
public Version resolve(VersionProvider provider) throws VersionNotFoundException {
|
||||
if (isResolved()) return this;
|
||||
return resolve(provider, new HashSet<>()).setResolved();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class AsyncTaskExecutor extends TaskExecutor {
|
||||
|
||||
private CompletableFuture<Boolean> future;
|
||||
|
||||
public AsyncTaskExecutor(Task<?> task) {
|
||||
super(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskExecutor start() {
|
||||
taskListeners.forEach(TaskListener::onStart);
|
||||
future = executeTasks(Collections.singleton(firstTask))
|
||||
.thenApplyAsync(exception -> {
|
||||
boolean success = exception == null;
|
||||
|
||||
if (!success) {
|
||||
// We log exception stacktrace because some of exceptions occurred because of bugs.
|
||||
Logging.LOG.log(Level.WARNING, "An exception occurred in task execution", exception);
|
||||
|
||||
Throwable resolvedException = resolveException(exception);
|
||||
if (resolvedException instanceof RuntimeException &&
|
||||
!(resolvedException instanceof CancellationException) &&
|
||||
!(resolvedException instanceof JsonParseException)) {
|
||||
// Track uncaught RuntimeException which are thrown mostly by our mistake
|
||||
if (uncaughtExceptionHandler != null)
|
||||
uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), resolvedException);
|
||||
}
|
||||
}
|
||||
|
||||
taskListeners.forEach(it -> it.onStop(success, this));
|
||||
return success;
|
||||
})
|
||||
.exceptionally(e -> {
|
||||
Lang.handleUncaughtException(resolveException(e));
|
||||
return false;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test() {
|
||||
start();
|
||||
try {
|
||||
return future.get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void cancel() {
|
||||
// AsyncTaskExecutor does not support cancellation.
|
||||
}
|
||||
|
||||
private CompletableFuture<Exception> executeTasks(Collection<Task<?>> tasks) {
|
||||
if (tasks == null || tasks.isEmpty())
|
||||
return CompletableFuture.completedFuture(null);
|
||||
|
||||
return CompletableFuture.completedFuture(null)
|
||||
.thenComposeAsync(unused -> {
|
||||
totTask.addAndGet(tasks.size());
|
||||
|
||||
return CompletableFuture.allOf(tasks.stream()
|
||||
.map(task -> CompletableFuture.completedFuture(null)
|
||||
.thenComposeAsync(unused2 -> executeTask(task))
|
||||
).toArray(CompletableFuture<?>[]::new));
|
||||
})
|
||||
.thenApplyAsync(unused -> (Exception) null)
|
||||
.exceptionally(throwable -> {
|
||||
Throwable resolved = resolveException(throwable);
|
||||
if (resolved instanceof Exception) {
|
||||
return (Exception) resolved;
|
||||
} else {
|
||||
// If an error occurred, we just rethrow it.
|
||||
throw new CompletionException(throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<?> executeTask(Task<?> task) {
|
||||
return CompletableFuture.completedFuture(null)
|
||||
.thenComposeAsync(unused -> {
|
||||
task.setCancelled(this::isCancelled);
|
||||
task.setState(Task.TaskState.READY);
|
||||
|
||||
if (task.getSignificance().shouldLog())
|
||||
Logging.LOG.log(Level.FINE, "Executing task: " + task.getName());
|
||||
|
||||
taskListeners.forEach(it -> it.onReady(task));
|
||||
|
||||
if (task.doPreExecute()) {
|
||||
return CompletableFuture.runAsync(wrap(task::preExecute), task.getExecutor());
|
||||
} else {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
})
|
||||
.thenComposeAsync(unused -> executeTasks(task.getDependents()))
|
||||
.thenComposeAsync(dependentsException -> {
|
||||
boolean isDependentsSucceeded = dependentsException == null;
|
||||
|
||||
if (!isDependentsSucceeded && task.isRelyingOnDependents()) {
|
||||
task.setException(dependentsException);
|
||||
rethrow(dependentsException);
|
||||
}
|
||||
|
||||
if (isDependentsSucceeded)
|
||||
task.setDependentsSucceeded();
|
||||
|
||||
return CompletableFuture.runAsync(wrap(() -> {
|
||||
task.setState(Task.TaskState.RUNNING);
|
||||
taskListeners.forEach(it -> it.onRunning(task));
|
||||
task.execute();
|
||||
}), task.getExecutor()).whenComplete((unused, throwable) -> {
|
||||
task.setState(Task.TaskState.EXECUTED);
|
||||
rethrow(throwable);
|
||||
});
|
||||
})
|
||||
.thenComposeAsync(unused -> executeTasks(task.getDependencies()))
|
||||
.thenComposeAsync(dependenciesException -> {
|
||||
boolean isDependenciesSucceeded = dependenciesException == null;
|
||||
|
||||
if (isDependenciesSucceeded)
|
||||
task.setDependenciesSucceeded();
|
||||
|
||||
if (task.doPostExecute()) {
|
||||
return CompletableFuture.runAsync(wrap(task::postExecute), task.getExecutor())
|
||||
.thenApply(unused -> dependenciesException);
|
||||
} else {
|
||||
return CompletableFuture.completedFuture(dependenciesException);
|
||||
}
|
||||
})
|
||||
.thenAcceptAsync(dependenciesException -> {
|
||||
boolean isDependenciesSucceeded = dependenciesException == null;
|
||||
|
||||
if (!isDependenciesSucceeded && task.isRelyingOnDependencies()) {
|
||||
Logging.LOG.severe("Subtasks failed for " + task.getName());
|
||||
task.setException(dependenciesException);
|
||||
rethrow(dependenciesException);
|
||||
}
|
||||
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
Logging.LOG.log(Level.FINER, "Task finished: " + task.getName());
|
||||
}
|
||||
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, false));
|
||||
taskListeners.forEach(it -> it.onFinished(task));
|
||||
|
||||
task.setState(Task.TaskState.SUCCEEDED);
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
Throwable resolved = resolveException(throwable);
|
||||
if (resolved instanceof Exception) {
|
||||
Exception e = (Exception) resolved;
|
||||
if (e instanceof InterruptedException || e instanceof CancellationException) {
|
||||
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 {
|
||||
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.setState(Task.TaskState.FAILED);
|
||||
}
|
||||
|
||||
throw new CompletionException(resolved); // rethrow error
|
||||
});
|
||||
}
|
||||
|
||||
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 == null)
|
||||
return;
|
||||
if (e instanceof ExecutionException || e instanceof CompletionException) { // including UncheckedException and UncheckedThrowable
|
||||
rethrow(e.getCause());
|
||||
} else if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException) e;
|
||||
} else {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Runnable wrap(ExceptionalRunnable<?> runnable) {
|
||||
return () -> {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
rethrow(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Thread.UncaughtExceptionHandler uncaughtExceptionHandler = null;
|
||||
|
||||
public static void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {
|
||||
AsyncTaskExecutor.uncaughtExceptionHandler = uncaughtExceptionHandler;
|
||||
}
|
||||
}
|
@ -0,0 +1,254 @@
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class CancellableTaskExecutor extends TaskExecutor {
|
||||
|
||||
private final ConcurrentLinkedQueue<Future<?>> workerQueue = new ConcurrentLinkedQueue<>();
|
||||
private Executor scheduler = Schedulers.newThread();
|
||||
|
||||
public CancellableTaskExecutor(Task<?> task) {
|
||||
super(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskExecutor start() {
|
||||
taskListeners.forEach(TaskListener::onStart);
|
||||
workerQueue.add(Schedulers.schedule(scheduler, wrap(() -> {
|
||||
boolean flag = executeTasks(Collections.singleton(firstTask));
|
||||
taskListeners.forEach(it -> it.onStop(flag, this));
|
||||
})));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test() {
|
||||
taskListeners.forEach(TaskListener::onStart);
|
||||
AtomicBoolean flag = new AtomicBoolean(true);
|
||||
Future<?> future = Schedulers.schedule(scheduler, wrap(() -> {
|
||||
flag.set(executeTasks(Collections.singleton(firstTask)));
|
||||
taskListeners.forEach(it -> it.onStop(flag.get(), this));
|
||||
}));
|
||||
workerQueue.add(future);
|
||||
try {
|
||||
future.get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (ExecutionException | CancellationException ignored) {
|
||||
}
|
||||
return flag.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void cancel() {
|
||||
cancelled.set(true);
|
||||
|
||||
while (!workerQueue.isEmpty()) {
|
||||
Future<?> future = workerQueue.poll();
|
||||
if (future != null)
|
||||
future.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean executeTasks(Collection<? extends Task<?>> tasks) throws InterruptedException {
|
||||
if (tasks.isEmpty())
|
||||
return true;
|
||||
|
||||
totTask.addAndGet(tasks.size());
|
||||
AtomicBoolean success = new AtomicBoolean(true);
|
||||
CountDownLatch latch = new CountDownLatch(tasks.size());
|
||||
for (Task<?> task : tasks) {
|
||||
if (cancelled.get())
|
||||
return false;
|
||||
Invoker invoker = new Invoker(task, latch, success);
|
||||
try {
|
||||
Future<?> future = Schedulers.schedule(scheduler, invoker);
|
||||
workerQueue.add(future);
|
||||
} catch (RejectedExecutionException e) {
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
if (cancelled.get())
|
||||
return false;
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
}
|
||||
return success.get() && !cancelled.get();
|
||||
}
|
||||
|
||||
private boolean executeTask(Task<?> task) {
|
||||
task.setCancelled(this::isCancelled);
|
||||
|
||||
if (cancelled.get()) {
|
||||
task.setState(Task.TaskState.FAILED);
|
||||
task.setException(new CancellationException());
|
||||
return false;
|
||||
}
|
||||
|
||||
task.setState(Task.TaskState.READY);
|
||||
|
||||
if (task.getSignificance().shouldLog())
|
||||
Logging.LOG.log(Level.FINE, "Executing task: " + task.getName());
|
||||
|
||||
taskListeners.forEach(it -> it.onReady(task));
|
||||
|
||||
boolean flag = false;
|
||||
|
||||
try {
|
||||
if (task.doPreExecute()) {
|
||||
try {
|
||||
Schedulers.schedule(task.getExecutor(), wrap(task::preExecute)).get();
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof Exception)
|
||||
throw (Exception) e.getCause();
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
Collection<? extends Task<?>> dependents = task.getDependents();
|
||||
boolean doDependentsSucceeded = executeTasks(dependents);
|
||||
Exception dependentsException = dependents.stream().map(Task::getException).filter(Objects::nonNull).findAny().orElse(null);
|
||||
if (!doDependentsSucceeded && task.isRelyingOnDependents() || cancelled.get()) {
|
||||
task.setException(dependentsException);
|
||||
throw new CancellationException();
|
||||
}
|
||||
|
||||
if (doDependentsSucceeded)
|
||||
task.setDependentsSucceeded();
|
||||
|
||||
try {
|
||||
Schedulers.schedule(task.getExecutor(), wrap(() -> {
|
||||
task.setState(Task.TaskState.RUNNING);
|
||||
taskListeners.forEach(it -> it.onRunning(task));
|
||||
task.execute();
|
||||
})).get();
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof Exception)
|
||||
throw (Exception) e.getCause();
|
||||
else
|
||||
throw e;
|
||||
} finally {
|
||||
task.setState(Task.TaskState.EXECUTED);
|
||||
}
|
||||
|
||||
Collection<? extends Task<?>> dependencies = task.getDependencies();
|
||||
boolean doDependenciesSucceeded = executeTasks(dependencies);
|
||||
Exception dependenciesException = dependencies.stream().map(Task::getException).filter(Objects::nonNull).findAny().orElse(null);
|
||||
|
||||
if (doDependenciesSucceeded)
|
||||
task.setDependenciesSucceeded();
|
||||
|
||||
if (task.doPostExecute()) {
|
||||
try {
|
||||
Schedulers.schedule(task.getExecutor(), wrap(task::postExecute)).get();
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof Exception)
|
||||
throw (Exception) e.getCause();
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!doDependenciesSucceeded && task.isRelyingOnDependencies()) {
|
||||
Logging.LOG.severe("Subtasks failed for " + task.getName());
|
||||
task.setException(dependenciesException);
|
||||
throw new CancellationException();
|
||||
}
|
||||
|
||||
flag = true;
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
Logging.LOG.log(Level.FINER, "Task finished: " + task.getName());
|
||||
}
|
||||
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, false));
|
||||
taskListeners.forEach(it -> it.onFinished(task));
|
||||
} catch (InterruptedException e) {
|
||||
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));
|
||||
} catch (CancellationException | RejectedExecutionException e) {
|
||||
if (task.getException() == null)
|
||||
task.setException(e);
|
||||
} catch (Exception e) {
|
||||
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.setState(flag ? Task.TaskState.SUCCEEDED : Task.TaskState.FAILED);
|
||||
return flag;
|
||||
}
|
||||
|
||||
private static void rethrow(Throwable e) {
|
||||
if (e == null)
|
||||
return;
|
||||
if (e instanceof ExecutionException || e instanceof CompletionException) { // including UncheckedException and UncheckedThrowable
|
||||
rethrow(e.getCause());
|
||||
} else if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException) e;
|
||||
} else {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Runnable wrap(ExceptionalRunnable<?> runnable) {
|
||||
return () -> {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
rethrow(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private class Invoker implements Runnable {
|
||||
|
||||
private final Task<?> task;
|
||||
private final CountDownLatch latch;
|
||||
private final AtomicBoolean success;
|
||||
|
||||
public Invoker(Task<?> task, CountDownLatch latch, AtomicBoolean success) {
|
||||
this.task = task;
|
||||
this.latch = latch;
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.currentThread().setName(task.getName());
|
||||
if (!executeTask(task))
|
||||
success.set(false);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -19,9 +19,20 @@ package org.jackhuang.hmcl.task;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -92,4 +103,61 @@ public final class Schedulers {
|
||||
if (SINGLE_EXECUTOR != null)
|
||||
SINGLE_EXECUTOR.shutdownNow();
|
||||
}
|
||||
|
||||
public static Future<?> schedule(Executor executor, Runnable command) {
|
||||
if (executor instanceof ExecutorService) {
|
||||
return ((ExecutorService) executor).submit(command);
|
||||
}
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<Exception> wrapper = new AtomicReference<>();
|
||||
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
command.run();
|
||||
} catch (Exception e) {
|
||||
wrapper.set(e);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
Thread.interrupted(); // clear the `interrupted` flag to prevent from interrupting EventDispatch thread.
|
||||
});
|
||||
|
||||
return new Future<Void>() {
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return latch.getCount() == 0;
|
||||
}
|
||||
|
||||
private Void getImpl() throws ExecutionException {
|
||||
Exception e = wrapper.get();
|
||||
if (e != null)
|
||||
throw new ExecutionException(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void get() throws InterruptedException, ExecutionException {
|
||||
latch.await();
|
||||
return getImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
if (!latch.await(timeout, unit))
|
||||
throw new TimeoutException();
|
||||
return getImpl();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -327,18 +327,35 @@ public abstract class Task<T> {
|
||||
}
|
||||
|
||||
public final TaskExecutor executor() {
|
||||
return new TaskExecutor(this);
|
||||
return new AsyncTaskExecutor(this);
|
||||
}
|
||||
|
||||
public final TaskExecutor executor(boolean start) {
|
||||
TaskExecutor executor = new TaskExecutor(this);
|
||||
TaskExecutor executor = new AsyncTaskExecutor(this);
|
||||
if (start)
|
||||
executor.start();
|
||||
return executor;
|
||||
}
|
||||
|
||||
public final TaskExecutor executor(TaskListener taskListener) {
|
||||
TaskExecutor executor = new TaskExecutor(this);
|
||||
TaskExecutor executor = new AsyncTaskExecutor(this);
|
||||
executor.addTaskListener(taskListener);
|
||||
return executor;
|
||||
}
|
||||
|
||||
public final TaskExecutor cancellableExecutor() {
|
||||
return new CancellableTaskExecutor(this);
|
||||
}
|
||||
|
||||
public final TaskExecutor cancellableExecutor(boolean start) {
|
||||
TaskExecutor executor = new CancellableTaskExecutor(this);
|
||||
if (start)
|
||||
executor.start();
|
||||
return executor;
|
||||
}
|
||||
|
||||
public final TaskExecutor cancellableExecutor(TaskListener taskListener) {
|
||||
TaskExecutor executor = new CancellableTaskExecutor(this);
|
||||
executor.addTaskListener(taskListener);
|
||||
return executor;
|
||||
}
|
||||
|
@ -1,48 +1,18 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class TaskExecutor {
|
||||
|
||||
private final Task<?> firstTask;
|
||||
private final List<TaskListener> taskListeners = new LinkedList<>();
|
||||
private Exception exception;
|
||||
private final AtomicInteger totTask = new AtomicInteger(0);
|
||||
private CompletableFuture<Boolean> future;
|
||||
private final AtomicBoolean cancelled = new AtomicBoolean(false);
|
||||
public abstract class TaskExecutor {
|
||||
protected final Task<?> firstTask;
|
||||
protected final List<TaskListener> taskListeners = new LinkedList<>();
|
||||
protected final AtomicInteger totTask = new AtomicInteger(0);
|
||||
protected final AtomicBoolean cancelled = new AtomicBoolean(false);
|
||||
protected Exception exception;
|
||||
|
||||
public TaskExecutor(Task<?> task) {
|
||||
this.firstTask = task;
|
||||
@ -52,232 +22,29 @@ public final class TaskExecutor {
|
||||
taskListeners.add(taskListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reason why the task execution failed.
|
||||
* If cancelled, null is returned.
|
||||
*/
|
||||
@Nullable
|
||||
public Exception getException() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
public TaskExecutor start() {
|
||||
taskListeners.forEach(TaskListener::onStart);
|
||||
future = executeTasks(Collections.singleton(firstTask))
|
||||
.thenApplyAsync(exception -> {
|
||||
boolean success = exception == null;
|
||||
public abstract TaskExecutor start();
|
||||
|
||||
if (!success) {
|
||||
// We log exception stacktrace because some of exceptions occurred because of bugs.
|
||||
Logging.LOG.log(Level.WARNING, "An exception occurred in task execution", exception);
|
||||
|
||||
Throwable resolvedException = resolveException(exception);
|
||||
if (resolvedException instanceof RuntimeException &&
|
||||
!(resolvedException instanceof CancellationException) &&
|
||||
!(resolvedException instanceof JsonParseException)) {
|
||||
// Track uncaught RuntimeException which are thrown mostly by our mistake
|
||||
if (uncaughtExceptionHandler != null)
|
||||
uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), resolvedException);
|
||||
}
|
||||
}
|
||||
|
||||
taskListeners.forEach(it -> it.onStop(success, this));
|
||||
return success;
|
||||
})
|
||||
.exceptionally(e -> {
|
||||
Lang.handleUncaughtException(resolveException(e));
|
||||
return false;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean test() {
|
||||
start();
|
||||
try {
|
||||
return future.get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} 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;
|
||||
}
|
||||
public abstract boolean test();
|
||||
|
||||
/**
|
||||
* Cancel the subscription ant interrupt all tasks.
|
||||
*/
|
||||
public synchronized void cancel() {
|
||||
if (future == null) {
|
||||
throw new IllegalStateException("Cannot cancel a not started TaskExecutor");
|
||||
}
|
||||
|
||||
cancelled.set(true);
|
||||
future.cancel(true);
|
||||
}
|
||||
public abstract void cancel();
|
||||
|
||||
public boolean isCancelled() {
|
||||
return cancelled.get();
|
||||
}
|
||||
|
||||
private CompletableFuture<Exception> executeTasks(Collection<Task<?>> tasks) {
|
||||
if (tasks == null || tasks.isEmpty())
|
||||
return CompletableFuture.completedFuture(null);
|
||||
|
||||
return CompletableFuture.completedFuture(null)
|
||||
.thenComposeAsync(unused -> {
|
||||
totTask.addAndGet(tasks.size());
|
||||
|
||||
return CompletableFuture.allOf(tasks.stream()
|
||||
.map(task -> CompletableFuture.completedFuture(null)
|
||||
.thenComposeAsync(unused2 -> executeTask(task))
|
||||
).toArray(CompletableFuture<?>[]::new));
|
||||
})
|
||||
.thenApplyAsync(unused -> (Exception) null)
|
||||
.exceptionally(throwable -> {
|
||||
Throwable resolved = resolveException(throwable);
|
||||
if (resolved instanceof CancellationException) {
|
||||
throw (CancellationException)resolved;
|
||||
}
|
||||
if (resolved instanceof Exception) {
|
||||
return (Exception) resolved;
|
||||
} else {
|
||||
// If an error occurred, we just rethrow it.
|
||||
throw new CompletionException(throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<?> executeTask(Task<?> task) {
|
||||
return CompletableFuture.completedFuture(null)
|
||||
.thenComposeAsync(unused -> {
|
||||
task.setCancelled(this::isCancelled);
|
||||
task.setState(Task.TaskState.READY);
|
||||
|
||||
if (task.getSignificance().shouldLog())
|
||||
Logging.LOG.log(Level.FINE, "Executing task: " + task.getName());
|
||||
|
||||
taskListeners.forEach(it -> it.onReady(task));
|
||||
|
||||
if (task.doPreExecute()) {
|
||||
return CompletableFuture.runAsync(wrap(task::preExecute), task.getExecutor());
|
||||
} else {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
})
|
||||
.thenComposeAsync(unused -> executeTasks(task.getDependents()))
|
||||
.thenComposeAsync(dependentsException -> {
|
||||
boolean isDependentsSucceeded = dependentsException == null;
|
||||
|
||||
if (!isDependentsSucceeded && task.isRelyingOnDependents()) {
|
||||
task.setException(dependentsException);
|
||||
rethrow(dependentsException);
|
||||
}
|
||||
|
||||
if (isDependentsSucceeded)
|
||||
task.setDependentsSucceeded();
|
||||
|
||||
return CompletableFuture.runAsync(wrap(() -> {
|
||||
task.setState(Task.TaskState.RUNNING);
|
||||
taskListeners.forEach(it -> it.onRunning(task));
|
||||
task.execute();
|
||||
}), task.getExecutor()).whenComplete((unused, throwable) -> {
|
||||
task.setState(Task.TaskState.EXECUTED);
|
||||
rethrow(throwable);
|
||||
});
|
||||
})
|
||||
.thenComposeAsync(unused -> executeTasks(task.getDependencies()))
|
||||
.thenComposeAsync(dependenciesException -> {
|
||||
boolean isDependenciesSucceeded = dependenciesException == null;
|
||||
|
||||
if (isDependenciesSucceeded)
|
||||
task.setDependenciesSucceeded();
|
||||
|
||||
if (task.doPostExecute()) {
|
||||
return CompletableFuture.runAsync(wrap(task::postExecute), task.getExecutor())
|
||||
.thenApply(unused -> dependenciesException);
|
||||
} else {
|
||||
return CompletableFuture.completedFuture(dependenciesException);
|
||||
}
|
||||
})
|
||||
.thenAcceptAsync(dependenciesException -> {
|
||||
boolean isDependenciesSucceeded = dependenciesException == null;
|
||||
|
||||
if (!isDependenciesSucceeded && task.isRelyingOnDependencies()) {
|
||||
Logging.LOG.severe("Subtasks failed for " + task.getName());
|
||||
task.setException(dependenciesException);
|
||||
rethrow(dependenciesException);
|
||||
}
|
||||
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
Logging.LOG.log(Level.FINER, "Task finished: " + task.getName());
|
||||
}
|
||||
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, false));
|
||||
taskListeners.forEach(it -> it.onFinished(task));
|
||||
|
||||
task.setState(Task.TaskState.SUCCEEDED);
|
||||
})
|
||||
.exceptionally(throwable -> {
|
||||
Throwable resolved = resolveException(throwable);
|
||||
if (resolved instanceof Exception) {
|
||||
Exception e = (Exception) resolved;
|
||||
if (e instanceof InterruptedException || e instanceof CancellationException) {
|
||||
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 {
|
||||
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.setState(Task.TaskState.FAILED);
|
||||
}
|
||||
|
||||
throw new CompletionException(resolved); // rethrow error
|
||||
});
|
||||
}
|
||||
|
||||
public int getRunningTasks() {
|
||||
return totTask.get();
|
||||
}
|
||||
|
||||
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 == null)
|
||||
return;
|
||||
if (e instanceof ExecutionException || e instanceof CompletionException) { // including UncheckedException and UncheckedThrowable
|
||||
rethrow(e.getCause());
|
||||
} else if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException) e;
|
||||
} else {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Runnable wrap(ExceptionalRunnable<?> runnable) {
|
||||
return () -> {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
rethrow(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Thread.UncaughtExceptionHandler uncaughtExceptionHandler = null;
|
||||
|
||||
public static void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {
|
||||
TaskExecutor.uncaughtExceptionHandler = uncaughtExceptionHandler;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user