diff --git a/base/src/main/java/de/staropensource/sosengine/base/Engine.java b/base/src/main/java/de/staropensource/sosengine/base/Engine.java index a271573..b62c4a6 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/Engine.java +++ b/base/src/main/java/de/staropensource/sosengine/base/Engine.java @@ -344,7 +344,7 @@ public final class Engine extends SubsystemClass { public void startThreads() { logger.diag("Starting threads"); - Logger.startLoggingThread(); + LoggingThread.startLoggingThread(); } /** diff --git a/base/src/main/java/de/staropensource/sosengine/base/EngineConfiguration.java b/base/src/main/java/de/staropensource/sosengine/base/EngineConfiguration.java index 5525a36..4808669 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/EngineConfiguration.java +++ b/base/src/main/java/de/staropensource/sosengine/base/EngineConfiguration.java @@ -24,6 +24,7 @@ import de.staropensource.sosengine.base.implementable.Configuration; import de.staropensource.sosengine.base.implementable.helper.EventHelper; import de.staropensource.sosengine.base.logging.CrashHandler; import de.staropensource.sosengine.base.logging.Logger; +import de.staropensource.sosengine.base.logging.LoggingThread; import de.staropensource.sosengine.base.type.EngineState; import de.staropensource.sosengine.base.type.logging.LogLevel; import de.staropensource.sosengine.base.type.vector.Vec2f; @@ -327,7 +328,7 @@ public final class EngineConfiguration extends Configuration { // Start logging thread automatically if (optimizeLogging && Engine.getInstance().getState() == EngineState.RUNNING) - Logger.startLoggingThread(); + LoggingThread.startLoggingThread(); } case "optimizeEvents" -> optimizeEvents = parser.getBoolean(group + property); case "optimizeSubsystemInitialization" -> optimizeSubsystemInitialization = parser.getBoolean(group + property); diff --git a/base/src/main/java/de/staropensource/sosengine/base/logging/Logger.java b/base/src/main/java/de/staropensource/sosengine/base/logging/Logger.java index 15856da..7905769 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/logging/Logger.java +++ b/base/src/main/java/de/staropensource/sosengine/base/logging/Logger.java @@ -21,18 +21,17 @@ package de.staropensource.sosengine.base.logging; import de.staropensource.sosengine.base.Engine; import de.staropensource.sosengine.base.EngineConfiguration; +import de.staropensource.sosengine.base.event.LogEvent; import de.staropensource.sosengine.base.implementable.LoggingAdapter; import de.staropensource.sosengine.base.implementable.helper.EventHelper; -import de.staropensource.sosengine.base.event.LogEvent; +import de.staropensource.sosengine.base.implementation.logging.PlainLoggingAdapter; +import de.staropensource.sosengine.base.implementation.shortcode.EmptyShortcodeConverter; import de.staropensource.sosengine.base.internal.implementation.placeholder.logger.*; import de.staropensource.sosengine.base.internal.type.QueuedLogMessage; -import de.staropensource.sosengine.base.implementation.logging.PlainLoggingAdapter; -import de.staropensource.sosengine.base.type.EngineState; import de.staropensource.sosengine.base.type.logging.LogLevel; import de.staropensource.sosengine.base.type.logging.LogRule; import de.staropensource.sosengine.base.type.logging.LogRuleType; import de.staropensource.sosengine.base.utility.PlaceholderEngine; -import de.staropensource.sosengine.base.implementation.shortcode.EmptyShortcodeConverter; import lombok.Getter; import lombok.Setter; import org.jetbrains.annotations.NotNull; @@ -53,20 +52,6 @@ import java.util.Scanner; */ @SuppressWarnings({ "JavadocDeclaration" }) public final class Logger { - - /** - * Contains a reference to the logging thread. - * - * @since v1-alpha1 - * -- GETTER -- - * Returns a reference the logging thread. - * - * @return logging thread reference - * @since v1-alpha1 - */ - @Getter - private static @Nullable Thread loggingThread; - /** * Contains a list of {@link QueuedLogMessage}s. * @@ -116,46 +101,6 @@ public final class Logger { */ public Logger() {} - /** - * Starts the logging thread. - * - * @since v1-alpha1 - * @see #getLoggingThread() - */ - public static void startLoggingThread() { - if (loggingThread == null) { - // Logging thread not defined, create and start new one - Runnable threadLogic = () -> { - while (true) { // Run in loop - // Stop thread when engine is shutting down - if (Engine.getInstance().getState() == EngineState.SHUTDOWN - || Engine.getInstance().getState() == EngineState.CRASHED - || !EngineConfiguration.getInstance().isOptimizeLogging()) - return; - - // Process all log messages - flushLogMessages(); - - // Sleep for whatever has been configured - long sleepDuration = System.currentTimeMillis() + EngineConfiguration.getInstance().getLoggerPollingSpeed(); - while (System.currentTimeMillis() < sleepDuration) - Thread.onSpinWait(); - } - }; - - loggingThread = Thread - .ofPlatform() - .daemon() - .name("Logging thread") - .group(Engine.getThreadGroup()) - .stackSize(10) - .start(threadLogic); - } else - // Restart logging thread if dead - if (!loggingThread.isAlive()) - loggingThread.start(); - } - /** * Prints all queued log messages. * diff --git a/base/src/main/java/de/staropensource/sosengine/base/logging/LoggingThread.java b/base/src/main/java/de/staropensource/sosengine/base/logging/LoggingThread.java new file mode 100644 index 0000000..0ed23b8 --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/logging/LoggingThread.java @@ -0,0 +1,126 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU Affero General Public License v3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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.sosengine.base.logging; + +import de.staropensource.sosengine.base.Engine; +import de.staropensource.sosengine.base.EngineConfiguration; +import de.staropensource.sosengine.base.type.EngineState; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; + +/** + * Controls the logging thread of the engine. + * + * @since v1-alpha4 + */ +@Slf4j +@SuppressWarnings({ "JavadocDeclaration" }) +public class LoggingThread { + /** + * Contains the {@link LoggerInstance} for this instance. + * + * @see LoggerInstance + * @since v1-alpha4 + */ + protected static final @NotNull LoggerInstance logger = new LoggerInstance.Builder().setClazz(LoggingThread.class).setOrigin("ENGINE").build(); + + /** + * Contains the logic of the logging thread. + * + * @since v1-alpha4 + */ + private static final @NotNull Runnable loggingThreadLogic = () -> { + while (!Thread.currentThread().isInterrupted()) { + // Stop thread when engine is shutting down + if (Engine.getInstance().getState() == EngineState.SHUTDOWN + || Engine.getInstance().getState() == EngineState.CRASHED + || !EngineConfiguration.getInstance().isOptimizeLogging()) + return; + + // Process all log messages + Logger.flushLogMessages(); + + // Sleep for whatever has been configured + if (EngineConfiguration.getInstance().getLoggerPollingSpeed() > 0) { + long sleepDuration = System.currentTimeMillis() + EngineConfiguration.getInstance().getLoggerPollingSpeed(); + while (System.currentTimeMillis() < sleepDuration) + Thread.onSpinWait(); + } + } + }; + + /** + * Contains a reference to the logging thread. + * + * @since v1-alpha1 + */ + private static Thread loggingThread; + + /** + * Reconstructs the {@link #loggingThread} thread. + * + * @throws IllegalStateException if the logging thread's state is not {@link Thread.State#TERMINATED} + * @since v1-alpha4 + */ + private static void reconstructThread() throws IllegalStateException { + if (loggingThread != null && loggingThread.getState() != Thread.State.TERMINATED) + throw new IllegalStateException("The logging thread needs to be terminated before reconstruction"); + + loggingThread = Thread + .ofPlatform() + .daemon() + .name("Logging thread") + .group(Engine.getThreadGroup()) + .stackSize(10) + .unstarted(loggingThreadLogic); + } + + /** + * Starts the logging thread. + * + * @since v1-alpha4 + * @see #loggingThread + */ + public static void startLoggingThread() { + if (loggingThread == null) + reconstructThread(); + + if (loggingThread.isAlive()) { + // Executing the thread restart logic prevents blocking the current thread + // while still ensuring that the logging thread is properly restarted + Thread + .ofVirtual() + .name("Logging thread restart thread") + .start(() -> { + logger.diag("Restarting the logging thread"); + + loggingThread.interrupt(); + + // Make sure that the logging thread is dead before reconstructing and starting it + while (loggingThread.isAlive()) + Thread.onSpinWait(); + + reconstructThread(); + loggingThread.start(); + }); + } else + loggingThread.start(); + } +}