Task chain exception catching
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user