Merge pull request #307 from solonovamax/fix/commands-completing-before-execution

Fix cloud swallowing exceptions in `suspend` methods.
This commit is contained in:
solo 2021-10-04 08:14:58 -04:00 committed by Jason
parent ad80933a20
commit 8711d19703
3 changed files with 55 additions and 37 deletions

View file

@ -29,8 +29,8 @@ import cloud.commandframework.context.CommandContext
import cloud.commandframework.execution.CommandExecutionCoordinator
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.future.future
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
import java.util.concurrent.CompletableFuture
import java.util.function.Predicate
@ -71,12 +71,15 @@ private class KotlinMethodCommandExecutionHandler<C>(
override fun executeFuture(commandContext: CommandContext<C>): CompletableFuture<Void?> {
val instance = context().instance()
val params = createParameterValues(commandContext, commandContext.flags(), false)
// We need to propagate exceptions to the caller.
return coroutineScope
.async<Void?>(this@KotlinMethodCommandExecutionHandler.coroutineContext) {
context().method().kotlinFunction?.callSuspend(instance, *params.toTypedArray())
null
return coroutineScope.future(this@KotlinMethodCommandExecutionHandler.coroutineContext) {
try {
context().method().kotlinFunction!!.callSuspend(instance, *params.toTypedArray())
} catch (e: InvocationTargetException) { // unwrap invocation exception
e.cause?.let { throw it } ?: throw e // if cause exists, throw, else rethrow invocation exception
}
.asCompletableFuture()
null
}
}
}

View file

@ -26,6 +26,7 @@ package cloud.commandframework.kotlin.coroutines
import cloud.commandframework.CommandManager
import cloud.commandframework.annotations.AnnotationParser
import cloud.commandframework.annotations.CommandMethod
import cloud.commandframework.exceptions.CommandExecutionException
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator
import cloud.commandframework.internal.CommandRegistrationHandler
import cloud.commandframework.meta.CommandMeta
@ -36,13 +37,15 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
class KotlinAnnotatedMethodsTest {
companion object {
val executorService = Executors.newSingleThreadExecutor()
val executorService: ExecutorService = Executors.newSingleThreadExecutor()
}
private lateinit var commandManager: CommandManager<TestCommandSender>
@ -68,15 +71,27 @@ class KotlinAnnotatedMethodsTest {
commandManager.executeCommand(TestCommandSender(), "test").await()
}
@Test
fun `test suspending command methods with exception`(): Unit = runBlocking {
AnnotationParser(commandManager, TestCommandSender::class.java) {
SimpleCommandMeta.empty()
}
.also { it.installCoroutineSupport() }
.parse(CommandMethods())
assertThrows<CommandExecutionException> {
commandManager.executeCommand(TestCommandSender(), "test-exception").await()
}
}
private class TestCommandSender
private class TestCommandManager :
CommandManager<TestCommandSender>(
AsynchronousCommandExecutionCoordinator.newBuilder<TestCommandSender>()
.withExecutor(executorService)
.build(),
CommandRegistrationHandler.nullCommandRegistrationHandler()
) {
private class TestCommandManager : CommandManager<TestCommandSender>(
AsynchronousCommandExecutionCoordinator.newBuilder<TestCommandSender>()
.withExecutor(executorService)
.build(),
CommandRegistrationHandler.nullCommandRegistrationHandler()
) {
override fun hasPermission(sender: TestCommandSender, permission: String): Boolean = true
@ -90,5 +105,8 @@ class KotlinAnnotatedMethodsTest {
withContext(Dispatchers.Default) {
println("called from thread: ${Thread.currentThread().name}")
}
@CommandMethod("test-exception")
public suspend fun suspendingCommandWithException(): Unit = throw IllegalStateException()
}
}