Merge pull request #307 from solonovamax/fix/commands-completing-before-execution
Fix cloud swallowing exceptions in `suspend` methods.
This commit is contained in:
parent
ad80933a20
commit
8711d19703
3 changed files with 55 additions and 37 deletions
|
|
@ -89,6 +89,8 @@ public final class AsynchronousCommandExecutionCoordinator<C> extends CommandExe
|
||||||
resultFuture.completeExceptionally(new CommandExecutionException(throwable, commandContext));
|
resultFuture.completeExceptionally(new CommandExecutionException(throwable, commandContext));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Only complete when the execution is actually finished. See #306 for more info.
|
||||||
|
resultFuture.complete(new CommandResult<>(commandContext));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -97,30 +99,25 @@ public final class AsynchronousCommandExecutionCoordinator<C> extends CommandExe
|
||||||
final @NonNull Pair<@Nullable Command<C>, @Nullable Exception> pair =
|
final @NonNull Pair<@Nullable Command<C>, @Nullable Exception> pair =
|
||||||
this.getCommandTree().parse(commandContext, input);
|
this.getCommandTree().parse(commandContext, input);
|
||||||
if (pair.getSecond() != null) {
|
if (pair.getSecond() != null) {
|
||||||
final CompletableFuture<CommandResult<C>> future = new CompletableFuture<>();
|
resultFuture.completeExceptionally(pair.getSecond());
|
||||||
future.completeExceptionally(pair.getSecond());
|
} else {
|
||||||
return future;
|
this.executor.execute(() -> commandConsumer.accept(pair.getFirst()));
|
||||||
}
|
}
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
} else {
|
||||||
commandConsumer.accept(pair.getFirst());
|
this.executor.execute(() -> {
|
||||||
return new CommandResult<>(commandContext);
|
try {
|
||||||
}, this.executor);
|
final @NonNull Pair<@Nullable Command<C>, @Nullable Exception> pair =
|
||||||
}
|
this.getCommandTree().parse(commandContext, input);
|
||||||
|
if (pair.getSecond() != null) {
|
||||||
this.executor.execute(() -> {
|
resultFuture.completeExceptionally(pair.getSecond());
|
||||||
try {
|
} else {
|
||||||
final @NonNull Pair<@Nullable Command<C>, @Nullable Exception> pair =
|
commandConsumer.accept(pair.getFirst());
|
||||||
this.getCommandTree().parse(commandContext, input);
|
}
|
||||||
if (pair.getSecond() != null) {
|
} catch (final Exception e) {
|
||||||
resultFuture.completeExceptionally(pair.getSecond());
|
resultFuture.completeExceptionally(e);
|
||||||
} else {
|
|
||||||
commandConsumer.accept(pair.getFirst());
|
|
||||||
resultFuture.complete(new CommandResult<>(commandContext));
|
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
});
|
||||||
resultFuture.completeExceptionally(e);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return resultFuture;
|
return resultFuture;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ import cloud.commandframework.context.CommandContext
|
||||||
import cloud.commandframework.execution.CommandExecutionCoordinator
|
import cloud.commandframework.execution.CommandExecutionCoordinator
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.future.future
|
||||||
import kotlinx.coroutines.future.asCompletableFuture
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
|
|
@ -71,12 +71,15 @@ private class KotlinMethodCommandExecutionHandler<C>(
|
||||||
override fun executeFuture(commandContext: CommandContext<C>): CompletableFuture<Void?> {
|
override fun executeFuture(commandContext: CommandContext<C>): CompletableFuture<Void?> {
|
||||||
val instance = context().instance()
|
val instance = context().instance()
|
||||||
val params = createParameterValues(commandContext, commandContext.flags(), false)
|
val params = createParameterValues(commandContext, commandContext.flags(), false)
|
||||||
|
|
||||||
// We need to propagate exceptions to the caller.
|
// We need to propagate exceptions to the caller.
|
||||||
return coroutineScope
|
return coroutineScope.future(this@KotlinMethodCommandExecutionHandler.coroutineContext) {
|
||||||
.async<Void?>(this@KotlinMethodCommandExecutionHandler.coroutineContext) {
|
try {
|
||||||
context().method().kotlinFunction?.callSuspend(instance, *params.toTypedArray())
|
context().method().kotlinFunction!!.callSuspend(instance, *params.toTypedArray())
|
||||||
null
|
} catch (e: InvocationTargetException) { // unwrap invocation exception
|
||||||
|
e.cause?.let { throw it } ?: throw e // if cause exists, throw, else rethrow invocation exception
|
||||||
}
|
}
|
||||||
.asCompletableFuture()
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ package cloud.commandframework.kotlin.coroutines
|
||||||
import cloud.commandframework.CommandManager
|
import cloud.commandframework.CommandManager
|
||||||
import cloud.commandframework.annotations.AnnotationParser
|
import cloud.commandframework.annotations.AnnotationParser
|
||||||
import cloud.commandframework.annotations.CommandMethod
|
import cloud.commandframework.annotations.CommandMethod
|
||||||
|
import cloud.commandframework.exceptions.CommandExecutionException
|
||||||
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator
|
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator
|
||||||
import cloud.commandframework.internal.CommandRegistrationHandler
|
import cloud.commandframework.internal.CommandRegistrationHandler
|
||||||
import cloud.commandframework.meta.CommandMeta
|
import cloud.commandframework.meta.CommandMeta
|
||||||
|
|
@ -36,13 +37,15 @@ import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
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.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class KotlinAnnotatedMethodsTest {
|
class KotlinAnnotatedMethodsTest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val executorService = Executors.newSingleThreadExecutor()
|
val executorService: ExecutorService = Executors.newSingleThreadExecutor()
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var commandManager: CommandManager<TestCommandSender>
|
private lateinit var commandManager: CommandManager<TestCommandSender>
|
||||||
|
|
@ -68,15 +71,27 @@ class KotlinAnnotatedMethodsTest {
|
||||||
commandManager.executeCommand(TestCommandSender(), "test").await()
|
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 TestCommandSender
|
||||||
|
|
||||||
private class TestCommandManager :
|
private class TestCommandManager : CommandManager<TestCommandSender>(
|
||||||
CommandManager<TestCommandSender>(
|
AsynchronousCommandExecutionCoordinator.newBuilder<TestCommandSender>()
|
||||||
AsynchronousCommandExecutionCoordinator.newBuilder<TestCommandSender>()
|
.withExecutor(executorService)
|
||||||
.withExecutor(executorService)
|
.build(),
|
||||||
.build(),
|
CommandRegistrationHandler.nullCommandRegistrationHandler()
|
||||||
CommandRegistrationHandler.nullCommandRegistrationHandler()
|
) {
|
||||||
) {
|
|
||||||
|
|
||||||
override fun hasPermission(sender: TestCommandSender, permission: String): Boolean = true
|
override fun hasPermission(sender: TestCommandSender, permission: String): Boolean = true
|
||||||
|
|
||||||
|
|
@ -90,5 +105,8 @@ class KotlinAnnotatedMethodsTest {
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
println("called from thread: ${Thread.currentThread().name}")
|
println("called from thread: ${Thread.currentThread().name}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CommandMethod("test-exception")
|
||||||
|
public suspend fun suspendingCommandWithException(): Unit = throw IllegalStateException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue