From c1090c1074f793315bddc1c509120fda2054975f Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Thu, 20 Aug 2020 12:17:31 +0800 Subject: [PATCH 1/3] Thread safe. (#114) * Thread safe. * Fix OOM in Java x32 --- .../mirai/console/command/CommandManager.kt | 3 + .../console/pure/BufferedOutputStream.kt | 2 +- .../mamoe/mirai/console/pure/ConsoleThread.kt | 113 ++++++++++++++++++ .../mamoe/mirai/console/pure/ConsoleUtils.kt | 1 + .../console/pure/MiraiConsoleFrontEndPure.kt | 24 ++-- .../console/pure/MiraiConsolePureLoader.kt | 52 +------- 6 files changed, 130 insertions(+), 65 deletions(-) create mode 100644 frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index 0c5f51381..743709545 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -152,6 +152,9 @@ public interface CommandManager { override suspend fun CommandSender.executeCommand(message: MessageChain): Command? = CommandManagerImpl.run { executeCommand(message) } + override val commandPrefix: String + get() = CommandManagerImpl.commandPrefix + override suspend fun Command.execute( sender: CommandSender, args: MessageChain, diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/BufferedOutputStream.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/BufferedOutputStream.kt index c88c8039c..269b03312 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/BufferedOutputStream.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/BufferedOutputStream.kt @@ -15,7 +15,7 @@ import java.io.OutputStream private const val LN = 10.toByte() internal class BufferedOutputStream @JvmOverloads constructor( - private val size: Int = 1024 * 1024 * 1024, + private val size: Int = 1024 * 1024, private val logger: (String?) -> Unit ) : ByteArrayOutputStream(size + 1) { override fun write(b: Int) { diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt new file mode 100644 index 000000000..2fcb0921a --- /dev/null +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions license that can be found via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.console.pure + +import kotlinx.coroutines.* +import net.mamoe.mirai.console.MiraiConsole +import net.mamoe.mirai.console.command.* +import net.mamoe.mirai.console.command.Command.Companion.primaryName +import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommandDetailed +import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register +import net.mamoe.mirai.console.util.ConsoleInternalAPI +import net.mamoe.mirai.utils.DefaultLogger +import org.fusesource.jansi.Ansi +import java.util.* +import java.util.concurrent.Executors +import kotlin.concurrent.thread + +@ConsoleInternalAPI +internal fun startupConsoleThread() { + val service = Executors.newSingleThreadExecutor { code -> + thread(start = false, isDaemon = false, name = "Console Input", block = code::run) + } + val dispatch = service.asCoroutineDispatcher() + ConsoleUtils.miraiLineReader = { hint -> + withContext(dispatch) { + ConsoleUtils.lineReader.readLine( + if (hint.isNotEmpty()) { + ConsoleUtils.lineReader.printAbove( + Ansi.ansi() + .fgCyan().a(MiraiConsoleFrontEndPure.sdf.format(Date())).a(" ") + .fgMagenta().a(hint) + .reset() + .toString() + ) + "$hint > " + } else "> " + ) + } + } + + /* + object : AbstractCommand(ConsoleCommandOwner, "test") { + override val usage: String + get() = "? Why usage" + + override suspend fun CommandSender.onCommand(args: Array) { + withContext(Dispatchers.IO) { + launch { sendMessage("I1> " + MiraiConsole.frontEnd.requestInput("Value 1")) } + launch { sendMessage("I2> " + MiraiConsole.frontEnd.requestInput("Value 2")) } + } + } + + }.register(true) + */ + + CoroutineScope(dispatch).launch { + val consoleLogger = DefaultLogger("Console") + while (isActive) { + try { + val next = MiraiConsoleFrontEndPure.requestInput("").let { + when { + it.startsWith(CommandManager.commandPrefix) -> { + it + } + it == "?" -> CommandManager.commandPrefix + BuiltInCommands.Help.primaryName + else -> CommandManager.commandPrefix + it + } + } + if (next.isBlank()) { + continue + } + consoleLogger.debug("INPUT> $next") + val result = ConsoleCommandSenderImpl.executeCommandDetailed(next) + when (result.status) { + CommandExecuteStatus.SUCCESSFUL -> { + } + CommandExecuteStatus.EXECUTION_EXCEPTION -> { + result.exception?.printStackTrace() + } + CommandExecuteStatus.COMMAND_NOT_FOUND -> { + consoleLogger.warning("Unknown command: ${result.commandName}") + } + CommandExecuteStatus.PERMISSION_DENIED -> { + consoleLogger.warning("Permission denied.") + } + } + } catch (e: InterruptedException) { + return@launch + } catch (e: Throwable) { + consoleLogger.error("Unhandled exception", e) + } + } + }.let { consoleJob -> + MiraiConsole.job.invokeOnCompletion { + runCatching { + consoleJob.cancel() + }.exceptionOrNull()?.printStackTrace() + runCatching { + service.shutdownNow() + }.exceptionOrNull()?.printStackTrace() + runCatching { + ConsoleUtils.terminal.close() + }.exceptionOrNull()?.printStackTrace() + } + } +} \ No newline at end of file diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleUtils.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleUtils.kt index 596db154e..91c44ecc5 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleUtils.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleUtils.kt @@ -19,6 +19,7 @@ internal object ConsoleUtils { val lineReader: LineReader val terminal: Terminal + lateinit var miraiLineReader: suspend (String) -> String init { diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt index 6143fa96a..f6cf70256 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleFrontEndPure.kt @@ -22,6 +22,7 @@ package net.mamoe.mirai.console.pure //import net.mamoe.mirai.console.command.CommandManager //import net.mamoe.mirai.console.utils.MiraiConsoleFrontEnd +import io.ktor.utils.io.concurrent.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import net.mamoe.mirai.Bot @@ -71,9 +72,15 @@ object MiraiConsoleFrontEndPure : MiraiConsoleFrontEnd { const val COLOR_RESET = "\u001b[39;49m" // } - private val sdf by lazy { + internal val sdf by ThreadLocal.withInitial { + // SimpleDateFormat not thread safe. SimpleDateFormat("HH:mm:ss") } + + private operator fun ThreadLocal.getValue(thiz: Any, property: Any): T { + return this.get() + } + override val name: String get() = "Pure" override val version: String @@ -90,28 +97,19 @@ object MiraiConsoleFrontEndPure : MiraiConsoleFrontEnd { } override suspend fun requestInput(hint: String): String { - if (hint.isNotEmpty()) { - ConsoleUtils.lineReader.printAbove( - Ansi.ansi() - .fgCyan().a(sdf.format(Date())) - .fgMagenta().a(hint) - .toString() - ) - } - return withContext(Dispatchers.IO) { - ConsoleUtils.lineReader.readLine("> ") - } + return ConsoleUtils.miraiLineReader(hint) } override fun createLoginSolver(): LoginSolver { return DefaultLoginSolver( input = suspend { - requestInput("") + requestInput("LOGIN> ") } ) } } + /* class MiraiConsoleFrontEndPure : MiraiConsoleFrontEnd { private var requesting = false diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt index 318a8b10b..1bfb4421e 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt @@ -51,7 +51,7 @@ internal fun startup() { DefaultLogger = { MiraiConsoleFrontEndPure.loggerFor(it) } overrideSTD() MiraiConsoleImplementationPure().start() - startConsoleThread() + startupConsoleThread() } internal fun overrideSTD() { @@ -71,56 +71,6 @@ internal fun overrideSTD() { ) } -internal fun startConsoleThread() { - thread(name = "Console Input") { - val consoleLogger = DefaultLogger("Console") - try { - kotlinx.coroutines.runBlocking { - while (isActive) { - val next = MiraiConsoleFrontEndPure.requestInput("").let { - when { - it.startsWith(CommandManager.commandPrefix) -> { - it - } - it == "?" -> CommandManager.commandPrefix + BuiltInCommands.Help.primaryName - else -> CommandManager.commandPrefix + it - } - } - if (next.isBlank()) { - continue - } - consoleLogger.debug("INPUT> $next") - val result = ConsoleCommandSenderImpl.executeCommandDetailed(next) - when (result.status) { - CommandExecuteStatus.SUCCESSFUL -> { - } - CommandExecuteStatus.EXECUTION_EXCEPTION -> { - result.exception?.printStackTrace() - } - CommandExecuteStatus.COMMAND_NOT_FOUND -> { - consoleLogger.warning("Unknown command: ${result.commandName}") - } - CommandExecuteStatus.PERMISSION_DENIED -> { - consoleLogger.warning("Permission denied.") - } - - } - } - } - } catch (e: InterruptedException) { - return@thread - } - }.let { thread -> - MiraiConsole.job.invokeOnCompletion { - runCatching { - thread.interrupt() - }.exceptionOrNull()?.printStackTrace() - runCatching { - ConsoleUtils.terminal.close() - }.exceptionOrNull()?.printStackTrace() - } - } -} internal object ConsoleCommandSenderImpl : ConsoleCommandSender() { override suspend fun sendMessage(message: Message) { From 8f39f42e56597e6bacbe9038a670091f85034830 Mon Sep 17 00:00:00 2001 From: PeratX <1215714524@qq.com> Date: Thu, 20 Aug 2020 12:21:54 +0800 Subject: [PATCH 2/3] Fix ConsoleInternalApi in ConsoleThread --- .../mamoe/mirai/console/pure/ConsoleThread.kt | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt index 2fcb0921a..5447636f8 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt @@ -11,10 +11,11 @@ package net.mamoe.mirai.console.pure import kotlinx.coroutines.* import net.mamoe.mirai.console.MiraiConsole -import net.mamoe.mirai.console.command.* +import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.command.Command.Companion.primaryName +import net.mamoe.mirai.console.command.CommandExecuteStatus +import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommandDetailed -import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.utils.DefaultLogger import org.fusesource.jansi.Ansi @@ -22,7 +23,7 @@ import java.util.* import java.util.concurrent.Executors import kotlin.concurrent.thread -@ConsoleInternalAPI +@OptIn(ConsoleInternalAPI::class) internal fun startupConsoleThread() { val service = Executors.newSingleThreadExecutor { code -> thread(start = false, isDaemon = false, name = "Console Input", block = code::run) @@ -45,21 +46,6 @@ internal fun startupConsoleThread() { } } - /* - object : AbstractCommand(ConsoleCommandOwner, "test") { - override val usage: String - get() = "? Why usage" - - override suspend fun CommandSender.onCommand(args: Array) { - withContext(Dispatchers.IO) { - launch { sendMessage("I1> " + MiraiConsole.frontEnd.requestInput("Value 1")) } - launch { sendMessage("I2> " + MiraiConsole.frontEnd.requestInput("Value 2")) } - } - } - - }.register(true) - */ - CoroutineScope(dispatch).launch { val consoleLogger = DefaultLogger("Console") while (isActive) { @@ -110,4 +96,4 @@ internal fun startupConsoleThread() { }.exceptionOrNull()?.printStackTrace() } } -} \ No newline at end of file +} From ba91731576462d8f843c664076ccec11929177f3 Mon Sep 17 00:00:00 2001 From: PeratX <1215714524@qq.com> Date: Thu, 20 Aug 2020 12:26:13 +0800 Subject: [PATCH 3/3] Fix TestCommand deprecated warning --- .../kotlin/net/mamoe/mirai/console/command/TestCommand.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index c7098afa3..aa1d1f0c1 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -27,8 +27,8 @@ import net.mamoe.mirai.console.command.description.CommandArgumentParser import net.mamoe.mirai.console.initTestEnvironment import net.mamoe.mirai.console.internal.command.flattenCommandComponents import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.SingleMessage -import net.mamoe.mirai.message.data.toMessage import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test @@ -107,7 +107,7 @@ internal class TestCommand { @Test fun testSimpleArgsSplitting() = runBlocking { assertEquals(arrayOf("test", "ttt", "tt").contentToString(), withTesting> { - TestSimpleCommand.execute(sender, "test ttt tt".toMessage()) + TestSimpleCommand.execute(sender, PlainText("test ttt tt")) }.contentToString()) }