diff --git a/base/src/main/kotlin/de/staropensource/engine/base/implementation/logging/threading/CoroutineThreadingHandler.kt b/base/src/main/kotlin/de/staropensource/engine/base/implementation/logging/threading/CoroutineThreadingHandler.kt new file mode 100644 index 0000000..b81ec22 --- /dev/null +++ b/base/src/main/kotlin/de/staropensource/engine/base/implementation/logging/threading/CoroutineThreadingHandler.kt @@ -0,0 +1,125 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU General Public License v3. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.base.implementation.logging.threading + +import de.staropensource.engine.base.Engine.Companion.logger +import de.staropensource.engine.base.EngineConfiguration +import de.staropensource.engine.base.implementable.logging.ThreadingHandler +import de.staropensource.engine.base.logging.Processor +import de.staropensource.engine.base.type.logging.Call +import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlin.system.measureTimeMillis + +/** + * Uses a coroutine for processing log calls. + * + * @since v1-alpha10 + */ +@Suppress("Unused") +class CoroutineThreadingHandler : ThreadingHandler { + /** + * Determines whether the + * coroutine should be active. + * + * @since v1-alpha10 + */ + var active: Boolean = false + private set + + /** + * Contains the coroutine job. + * + * @since v1-alpha10 + */ + private var coroutine: Job? = null + + /** + * A [Mutex] protecting the [queue]. + * + * @since v1-alpha10 + */ + private val queueMutex: Mutex = Mutex(false) + + /** + * Contains all queued log [Call]s. + * + * @since v1-alpha10 + */ + private val queue: LinkedHashSet = linkedSetOf() + + + @OptIn(DelicateCoroutinesApi::class) + override fun start() { + active = true + GlobalScope.launch { + var time: ULong + var waitTime: ULong + while (active) { + time = measureTimeMillis { flushSuspend() }.toULong() + + if (EngineConfiguration.logThreadingPollDelay > 0u) + if (time > EngineConfiguration.logThreadingPollDelay) { + logger.warn("Logging coroutine is unable to keep up! Processing for last message batch took ${time}ms, which is longer than the configured logThreadingPollDelay of ${EngineConfiguration.logThreadingPollDelay}ms") + waitTime = 0u + } else + waitTime = EngineConfiguration.logThreadingPollDelay.minus(time) + else + waitTime = 0u + + if (waitTime > 0u) + delay(waitTime.toLong()) + } + } + } + + override fun stop() { + active = false + + while (coroutine!!.isActive) + Thread.onSpinWait() + } + + override fun queue(call: Call) { + runBlocking { + queueMutex.withLock { + queue.add(call) + } + } + } + + override fun flush() { + runBlocking { flushSuspend() } + } + + private suspend fun flushSuspend() { + val queue: LinkedHashSet + + queueMutex.withLock { + @Suppress("UNCHECKED_CAST") + queue = this@CoroutineThreadingHandler.queue.clone() as LinkedHashSet + this@CoroutineThreadingHandler.queue.clear() + } + + for (call: Call in queue) + Processor.process(call) + } +} diff --git a/base/src/main/kotlin/de/staropensource/engine/base/implementation/logging/threading/NativeThreadingHandler.kt b/base/src/main/kotlin/de/staropensource/engine/base/implementation/logging/threading/NativeThreadingHandler.kt new file mode 100644 index 0000000..4faa50e --- /dev/null +++ b/base/src/main/kotlin/de/staropensource/engine/base/implementation/logging/threading/NativeThreadingHandler.kt @@ -0,0 +1,129 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU General Public License v3. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.base.implementation.logging.threading + +import de.staropensource.engine.base.Engine.Companion.logger +import de.staropensource.engine.base.EngineConfiguration +import de.staropensource.engine.base.annotation.NonKotlinContact +import de.staropensource.engine.base.implementable.logging.ThreadingHandler +import de.staropensource.engine.base.logging.Processor +import de.staropensource.engine.base.type.logging.Call +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlin.system.measureTimeMillis + +/** + * Uses a thread for processing log calls. + * + * @since v1-alpha10 + */ +@NonKotlinContact +@Suppress("Unused") +class NativeThreadingHandler : ThreadingHandler { + /** + * Determines whether the logging + * thread should be active. + * + * @since v1-alpha10 + */ + var active: Boolean = false + private set + + /** + * Contains the logging thread. + * + * @since v1-alpha10 + */ + private var loggingThread: Thread? = null + + /** + * A [Mutex] protecting the [queue]. + * + * @since v1-alpha10 + */ + private val queueMutex: Mutex = Mutex(false) + + /** + * Contains all queued log [Call]s. + * + * @since v1-alpha10 + */ + private val queue: LinkedHashSet = linkedSetOf() + + + override fun start() { + active = true + loggingThread = Thread + .ofPlatform() + .daemon() + .name("Logging thread") + .uncaughtExceptionHandler { thread, throwable -> logger.crash("Logging thread terminated unexpectedly", throwable) } + .start { + var time: ULong + var waitTime: ULong + while (active) { + time = measureTimeMillis { flush() }.toULong() + + if (EngineConfiguration.logThreadingPollDelay > 0u) + if (time > EngineConfiguration.logThreadingPollDelay) { + logger.warn("Logging thread is unable to keep up! Processing for last message batch took ${time}ms, which is longer than the configured logThreadingPollDelay of ${EngineConfiguration.logThreadingPollDelay}ms") + waitTime = 0u + } else + waitTime = EngineConfiguration.logThreadingPollDelay.minus(time) + else + waitTime = 0u + + if (waitTime > 0u) + Thread.sleep(waitTime.toLong()) + } + } + } + + override fun stop() { + active = false + + while (loggingThread!!.isAlive) + Thread.onSpinWait() + } + + override fun queue(call: Call) { + runBlocking { + queueMutex.withLock { + queue.add(call) + } + } + } + + override fun flush() { + runBlocking { + val queue: LinkedHashSet + + queueMutex.withLock { + @Suppress("UNCHECKED_CAST") + queue = this@NativeThreadingHandler.queue.clone() as LinkedHashSet + this@NativeThreadingHandler.queue.clear() + } + + for (call: Call in queue) + Processor.process(call) + } + } +} diff --git a/testapp/src/main/kotlin/de/staropensource/engine/testapp/Main.kt b/testapp/src/main/kotlin/de/staropensource/engine/testapp/Main.kt index 61183e6..b675d92 100644 --- a/testapp/src/main/kotlin/de/staropensource/engine/testapp/Main.kt +++ b/testapp/src/main/kotlin/de/staropensource/engine/testapp/Main.kt @@ -22,6 +22,7 @@ package de.staropensource.engine.testapp import de.staropensource.engine.ansi.AnsiSubsystem import de.staropensource.engine.base.Engine import de.staropensource.engine.base.EngineConfiguration +import de.staropensource.engine.base.implementation.logging.threading.CoroutineThreadingHandler import de.staropensource.engine.base.logging.Logger import de.staropensource.engine.base.type.logging.Level @@ -55,6 +56,7 @@ class Main private constructor() { fun main(arguments: Array) { // Update engine configuration EngineConfiguration.logLevels = Level.entries.toMutableSet() + EngineConfiguration.logThreadingHandler = CoroutineThreadingHandler() // Register subsystems AnsiSubsystem.register()