Thread safe. (#114)

* Thread safe.

* Fix OOM in Java x32
This commit is contained in:
Karlatemp 2020-08-20 12:17:31 +08:00 committed by GitHub
parent 36fc942ace
commit c1090c1074
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 130 additions and 65 deletions

View File

@ -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,

View File

@ -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) {

View File

@ -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<out Any>) {
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()
}
}
}

View File

@ -19,6 +19,7 @@ internal object ConsoleUtils {
val lineReader: LineReader
val terminal: Terminal
lateinit var miraiLineReader: suspend (String) -> String
init {

View File

@ -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 <T> ThreadLocal<T>.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

View File

@ -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) {