Task chain exception catching

This commit is contained in:
huangyuhui
2017-08-26 19:09:21 +08:00
parent c775e8368e
commit b07144278b
12 changed files with 56 additions and 55 deletions

View File

@@ -50,7 +50,7 @@ fun readHMCLModpackManifest(f: File): Modpack {
object HMCLModpackManifest object HMCLModpackManifest
class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, private val modpack: Modpack, private val name: String): Task() { class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, modpack: Modpack, private val name: String): Task() {
private val dependency = profile.dependency private val dependency = profile.dependency
private val repository = profile.repository private val repository = profile.repository
override val dependencies = mutableListOf<Task>() override val dependencies = mutableListOf<Task>()
@@ -63,6 +63,8 @@ class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, privat
version = version.copy(jar = null) version = version.copy(jar = null)
dependents += dependency.gameBuilder().name(name).gameVersion(modpack.gameVersion!!).buildAsync() dependents += dependency.gameBuilder().name(name).gameVersion(modpack.gameVersion!!).buildAsync()
dependencies += VersionJSONSaveTask(repository, version) // override the json created by buildAsync() dependencies += VersionJSONSaveTask(repository, version) // override the json created by buildAsync()
onDone += { event -> if (event.failed) repository.removeVersionFromDisk(name) }
} }
private val run = repository.getRunDirectory(name) private val run = repository.getRunDirectory(name)
@@ -119,6 +121,11 @@ class HMCLModpackExportTask @JvmOverloads constructor(
private val modpack: Modpack, private val modpack: Modpack,
private val output: File, private val output: File,
override val id: String = ID): TaskResult<ZipEngine>() { override val id: String = ID): TaskResult<ZipEngine>() {
init {
onDone += { event -> if (event.failed) output.delete() }
}
override fun execute() { override fun execute() {
val blackList = ArrayList<String>(MODPACK_BLACK_LIST) val blackList = ArrayList<String>(MODPACK_BLACK_LIST)
blackList.add(version + ".jar") blackList.add(version + ".jar")

View File

@@ -94,26 +94,28 @@ interface AbstractWizardDisplayer : WizardDisplayer {
navigateTo(StackPane().apply { children += vbox }, Navigation.NavigationDirection.FINISH) navigateTo(StackPane().apply { children += vbox }, Navigation.NavigationDirection.FINISH)
task.executor().let { executor -> task.with(org.jackhuang.hmcl.task.task(Scheduler.JAVAFX) {
navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH)
}).executor().apply {
@Suppress("NAME_SHADOWING") @Suppress("NAME_SHADOWING")
executor.taskListener = object : TaskListener { taskListener = object : TaskListener {
override fun onReady(task: Task) { override fun onReady(task: Task) {
Platform.runLater { tasksBar.progressProperty().set(finishedTasks * 1.0 / executor.totTask.get()) } Platform.runLater { tasksBar.progressProperty().set(finishedTasks * 1.0 / totTask.get()) }
} }
override fun onFinished(task: Task) { override fun onFinished(task: Task) {
Platform.runLater { Platform.runLater {
label.text = task.title label.text = task.title
++finishedTasks ++finishedTasks
tasksBar.progressProperty().set(finishedTasks * 1.0 / executor.totTask.get()) tasksBar.progressProperty().set(finishedTasks * 1.0 / totTask.get())
} }
} }
override fun onFailed(task: Task) { override fun onFailed(task: Task, throwable: Throwable) {
Platform.runLater { Platform.runLater {
label.text = task.title label.text = task.title
++finishedTasks ++finishedTasks
tasksBar.progressProperty().set(finishedTasks * 1.0 / executor.totTask.get()) tasksBar.progressProperty().set(finishedTasks * 1.0 / totTask.get())
} }
} }
@@ -123,11 +125,7 @@ interface AbstractWizardDisplayer : WizardDisplayer {
} }
cancelQueue.add(executor) cancelQueue.add(this)
executor.submit(org.jackhuang.hmcl.task.task(Scheduler.JAVAFX) {
navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH)
})
}.start() }.start()
} }

View File

@@ -45,7 +45,7 @@ class DefaultGameBuilder(val dependencyManager: DefaultDependencyManager): GameB
GameLoggingDownloadTask(dependencyManager, version), GameLoggingDownloadTask(dependencyManager, version),
GameDownloadTask(version), GameDownloadTask(version),
GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries. GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries.
) then VersionJSONSaveTask(dependencyManager.repository, version) ) with VersionJSONSaveTask(dependencyManager.repository, version) // using [with] because download failure here are tolerant.
if (toolVersions.containsKey("forge")) if (toolVersions.containsKey("forge"))
result = result then libraryTaskHelper(gameVersion, "forge") result = result then libraryTaskHelper(gameVersion, "forge")

View File

@@ -43,6 +43,8 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
override val dependencies = mutableListOf<Task>() override val dependencies = mutableListOf<Task>()
override val id = "version" override val id = "version"
override val reliesOnDependencies = false
init { init {
if (!forgeVersionList.loaded) if (!forgeVersionList.loaded)
dependents += forgeVersionList.refreshAsync(dependencyManager.downloadProvider).then { dependents += forgeVersionList.refreshAsync(dependencyManager.downloadProvider).then {

View File

@@ -42,6 +42,8 @@ class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManage
override val dependencies = mutableListOf<Task>() override val dependencies = mutableListOf<Task>()
override val id = "version" override val id = "version"
override val reliesOnDependencies = false
init { init {
if (!optiFineVersionList.loaded) if (!optiFineVersionList.loaded)
dependents += optiFineVersionList.refreshAsync(dependencyManager.downloadProvider) then { dependents += optiFineVersionList.refreshAsync(dependencyManager.downloadProvider) then {

View File

@@ -213,6 +213,8 @@ class MMCModpackInstallTask(private val dependencyManager: DefaultDependencyMana
init { init {
check(!repository.hasVersion(name), { "Version $name already exists." }) check(!repository.hasVersion(name), { "Version $name already exists." })
dependents += dependencyManager.gameBuilder().name(name).gameVersion(manifest.gameVersion).buildAsync() dependents += dependencyManager.gameBuilder().name(name).gameVersion(manifest.gameVersion).buildAsync()
onDone += { event -> if (event.failed) repository.removeVersionFromDisk(name) }
} }
private val run = repository.getRunDirectory(name) private val run = repository.getRunDirectory(name)

View File

@@ -26,7 +26,7 @@ import org.jackhuang.hmcl.util.AutoTypingMap
* @param succ a callback that returns the task runs after [pred], [succ] will be executed asynchronously. You can do something that relies on the result of [pred]. * @param succ a callback that returns the task runs after [pred], [succ] will be executed asynchronously. You can do something that relies on the result of [pred].
* @param reliant true if this task chain will be broken when task [pred] fails. * @param reliant true if this task chain will be broken when task [pred] fails.
*/ */
internal class CoupleTask<P: Task>(pred: P, private val succ: (AutoTypingMap<String>) -> Task?, override val reliant: Boolean) : Task() { internal class CoupleTask<P: Task>(pred: P, private val succ: (AutoTypingMap<String>) -> Task?, override val reliesOnDependents: Boolean) : Task() {
override val hidden: Boolean = true override val hidden: Boolean = true
override val dependents: Collection<Task> = listOf(pred) override val dependents: Collection<Task> = listOf(pred)

View File

@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.task
/** /**
* The tasks that provides a way to execute tasks parallelly. * The tasks that provides a way to execute tasks parallelly.
* Fails when some of [tasks] failed.
* *
* @param tasks the tasks that can be executed parallelly. * @param tasks the tasks that can be executed parallelly.
*/ */

View File

@@ -44,11 +44,20 @@ abstract class Task {
open val scheduler: Scheduler = Scheduler.DEFAULT open val scheduler: Scheduler = Scheduler.DEFAULT
/** /**
* True if requires all dependent tasks finishing successfully. * True if requires all [dependents] finishing successfully.
* *
* **Note** if this field is set false, you are not supposed to invoke [run] * **Note** if this field is set false, you are not supposed to invoke [run]
* @defaultValue true
*/ */
open val reliant: Boolean = true open val reliesOnDependents: Boolean = true
/**
* True if requires all [dependencies] finishing successfully.
*
* **Note** if this field is set false, you are not supposed to invoke [run]
* @defaultValue false
*/
open val reliesOnDependencies: Boolean = true
open var title: String = this.javaClass.toString() open var title: String = this.javaClass.toString()
@@ -102,7 +111,7 @@ abstract class Task {
val onDone = EventManager<TaskEvent>() val onDone = EventManager<TaskEvent>()
/** /**
* **Note** reliant does not work here, which is always treated as true here. * **Note** [reliesOnDependents] and [reliesOnDependencies] does not work here, which is always treated as true here.
*/ */
@Throws(Exception::class) @Throws(Exception::class)
fun run() { fun run() {
@@ -120,12 +129,10 @@ abstract class Task {
this.progressPropertyImpl.unbind() this.progressPropertyImpl.unbind()
} }
fun executor() = TaskExecutor().submit(this) fun executor() = TaskExecutor(this)
fun executor(taskListener: TaskListener) = TaskExecutor().submit(this).apply { this.taskListener = taskListener } fun executor(taskListener: TaskListener) = TaskExecutor(this).apply { this.taskListener = taskListener }
fun start() = executor().start() fun start() = executor().start()
fun subscribe(subscriber: Task) = executor().apply { fun subscribe(subscriber: Task) = TaskExecutor(with(subscriber)).apply { start() }
submit(subscriber).start()
}
fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: (AutoTypingMap<String>) -> Unit) = subscribe(task(scheduler, closure)) fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: (AutoTypingMap<String>) -> Unit) = subscribe(task(scheduler, closure))

View File

@@ -27,46 +27,22 @@ import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import java.util.logging.Level import java.util.logging.Level
class TaskExecutor() { class TaskExecutor(private val task: Task) {
var taskListener: TaskListener? = null var taskListener: TaskListener? = null
var canceled = false var canceled = false
private set private set
val totTask = AtomicInteger(0) val totTask = AtomicInteger(0)
val variables = AutoTypingMap<String>(mutableMapOf()) val variables = AutoTypingMap<String>(mutableMapOf())
private val taskQueue = ConcurrentLinkedQueue<Task>() var lastException: Exception? = null
private val workerQueue = ConcurrentLinkedQueue<Future<*>>() private val workerQueue = ConcurrentLinkedQueue<Future<*>>()
/**
* Submit a task to subscription to run.
* You can submit a task even when started this subscription.
* Thread-safe function.
*/
fun submit(task: Task): TaskExecutor {
taskQueue.add(task)
return this
}
/** /**
* Start the subscription and run all registered tasks asynchronously. * Start the subscription and run all registered tasks asynchronously.
*/ */
fun start() { fun start() {
workerQueue.add(Scheduler.NEW_THREAD.schedule { workerQueue.add(Scheduler.NEW_THREAD.schedule {
totTask.addAndGet(taskQueue.size) if (!executeTasks(listOf(task)))
while (!taskQueue.isEmpty()) {
if (canceled) break
val task = taskQueue.poll()
if (task != null) {
val future = task.scheduler.schedule { executeTask(task) }
try {
future?.get()
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
break
}
}
}
if (canceled || Thread.interrupted())
taskListener?.onTerminate() taskListener?.onTerminate()
}) })
} }
@@ -120,34 +96,40 @@ class TaskExecutor() {
var flag = false var flag = false
try { try {
if (!doDependentsSucceeded && t.reliant || canceled) if (!doDependentsSucceeded && t.reliesOnDependents || canceled)
throw SilentException() throw SilentException()
t.variables = variables t.variables = variables
t.execute() t.execute()
if (t is TaskResult<*>) if (t is TaskResult<*>)
variables[t.id] = t.result variables[t.id] = t.result
if (!executeTasks(t.dependencies) && t.reliesOnDependencies)
throw IllegalStateException("Subtasks failed for ${t.title}")
flag = true flag = true
if (!t.hidden) if (!t.hidden)
LOG.finer("Task finished: ${t.title}") LOG.finer("Task finished: ${t.title}")
executeTasks(t.dependencies)
if (!t.hidden) { if (!t.hidden) {
t.onDone(TaskEvent(source = this, task = t, failed = false)) t.onDone(TaskEvent(source = this, task = t, failed = false))
taskListener?.onFinished(t) taskListener?.onFinished(t)
} }
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
if (!t.hidden) { if (!t.hidden) {
lastException = e
LOG.log(Level.FINE, "Task aborted: ${t.title}", e) LOG.log(Level.FINE, "Task aborted: ${t.title}", e)
t.onDone(TaskEvent(source = this, task = t, failed = true)) t.onDone(TaskEvent(source = this, task = t, failed = true))
taskListener?.onFailed(t) taskListener?.onFailed(t, e)
} }
} catch (e: SilentException) { } catch (e: SilentException) {
// nothing here // nothing here
} catch (e: Exception) { } catch (e: Exception) {
if (!t.hidden) { if (!t.hidden) {
lastException = e
LOG.log(Level.SEVERE, "Task failed: ${t.title}", e) LOG.log(Level.SEVERE, "Task failed: ${t.title}", e)
t.onDone(TaskEvent(source = this, task = t, failed = true)) t.onDone(TaskEvent(source = this, task = t, failed = true))
taskListener?.onFailed(t) taskListener?.onFailed(t, e)
} }
} finally { } finally {
t.variables = null t.variables = null

View File

@@ -22,6 +22,6 @@ import java.util.*
interface TaskListener : EventListener { interface TaskListener : EventListener {
fun onReady(task: Task) {} fun onReady(task: Task) {}
fun onFinished(task: Task) {} fun onFinished(task: Task) {}
fun onFailed(task: Task) {} fun onFailed(task: Task, throwable: Throwable) {}
fun onTerminate() {} fun onTerminate() {}
} }

View File

@@ -141,7 +141,7 @@ class Test {
override fun onFinished(task: Task) { override fun onFinished(task: Task) {
} }
override fun onFailed(task: Task) { override fun onFailed(task: Task, throwable: Throwable) {
} }
override fun onTerminate() { override fun onTerminate() {