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
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 repository = profile.repository
override val dependencies = mutableListOf<Task>()
@ -63,6 +63,8 @@ class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, privat
version = version.copy(jar = null)
dependents += dependency.gameBuilder().name(name).gameVersion(modpack.gameVersion!!).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)
@ -119,6 +121,11 @@ class HMCLModpackExportTask @JvmOverloads constructor(
private val modpack: Modpack,
private val output: File,
override val id: String = ID): TaskResult<ZipEngine>() {
init {
onDone += { event -> if (event.failed) output.delete() }
}
override fun execute() {
val blackList = ArrayList<String>(MODPACK_BLACK_LIST)
blackList.add(version + ".jar")

View File

@ -94,26 +94,28 @@ interface AbstractWizardDisplayer : WizardDisplayer {
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")
executor.taskListener = object : TaskListener {
taskListener = object : TaskListener {
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) {
Platform.runLater {
label.text = task.title
++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 {
label.text = task.title
++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)
executor.submit(org.jackhuang.hmcl.task.task(Scheduler.JAVAFX) {
navigateTo(Label("Successful"), Navigation.NavigationDirection.FINISH)
})
cancelQueue.add(this)
}.start()
}

View File

@ -45,7 +45,7 @@ class DefaultGameBuilder(val dependencyManager: DefaultDependencyManager): GameB
GameLoggingDownloadTask(dependencyManager, version),
GameDownloadTask(version),
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"))
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 id = "version"
override val reliesOnDependencies = false
init {
if (!forgeVersionList.loaded)
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 id = "version"
override val reliesOnDependencies = false
init {
if (!optiFineVersionList.loaded)
dependents += optiFineVersionList.refreshAsync(dependencyManager.downloadProvider) then {

View File

@ -213,6 +213,8 @@ class MMCModpackInstallTask(private val dependencyManager: DefaultDependencyMana
init {
check(!repository.hasVersion(name), { "Version $name already exists." })
dependents += dependencyManager.gameBuilder().name(name).gameVersion(manifest.gameVersion).buildAsync()
onDone += { event -> if (event.failed) repository.removeVersionFromDisk(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 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 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.
* Fails when some of [tasks] failed.
*
* @param tasks the tasks that can be executed parallelly.
*/

View File

@ -44,11 +44,20 @@ abstract class Task {
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]
* @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()
@ -102,7 +111,7 @@ abstract class Task {
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)
fun run() {
@ -120,12 +129,10 @@ abstract class Task {
this.progressPropertyImpl.unbind()
}
fun executor() = TaskExecutor().submit(this)
fun executor(taskListener: TaskListener) = TaskExecutor().submit(this).apply { this.taskListener = taskListener }
fun executor() = TaskExecutor(this)
fun executor(taskListener: TaskListener) = TaskExecutor(this).apply { this.taskListener = taskListener }
fun start() = executor().start()
fun subscribe(subscriber: Task) = executor().apply {
submit(subscriber).start()
}
fun subscribe(subscriber: Task) = TaskExecutor(with(subscriber)).apply { start() }
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.logging.Level
class TaskExecutor() {
class TaskExecutor(private val task: Task) {
var taskListener: TaskListener? = null
var canceled = false
private set
val totTask = AtomicInteger(0)
val variables = AutoTypingMap<String>(mutableMapOf())
private val taskQueue = ConcurrentLinkedQueue<Task>()
var lastException: Exception? = null
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.
*/
fun start() {
workerQueue.add(Scheduler.NEW_THREAD.schedule {
totTask.addAndGet(taskQueue.size)
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())
if (!executeTasks(listOf(task)))
taskListener?.onTerminate()
})
}
@ -120,34 +96,40 @@ class TaskExecutor() {
var flag = false
try {
if (!doDependentsSucceeded && t.reliant || canceled)
if (!doDependentsSucceeded && t.reliesOnDependents || canceled)
throw SilentException()
t.variables = variables
t.execute()
if (t is TaskResult<*>)
variables[t.id] = t.result
if (!executeTasks(t.dependencies) && t.reliesOnDependencies)
throw IllegalStateException("Subtasks failed for ${t.title}")
flag = true
if (!t.hidden)
LOG.finer("Task finished: ${t.title}")
executeTasks(t.dependencies)
if (!t.hidden) {
t.onDone(TaskEvent(source = this, task = t, failed = false))
taskListener?.onFinished(t)
}
} catch (e: InterruptedException) {
if (!t.hidden) {
lastException = e
LOG.log(Level.FINE, "Task aborted: ${t.title}", e)
t.onDone(TaskEvent(source = this, task = t, failed = true))
taskListener?.onFailed(t)
taskListener?.onFailed(t, e)
}
} catch (e: SilentException) {
// nothing here
} catch (e: Exception) {
if (!t.hidden) {
lastException = e
LOG.log(Level.SEVERE, "Task failed: ${t.title}", e)
t.onDone(TaskEvent(source = this, task = t, failed = true))
taskListener?.onFailed(t)
taskListener?.onFailed(t, e)
}
} finally {
t.variables = null

View File

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

View File

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