From 2fedc981af39087025bf74d68c7c3de993738858 Mon Sep 17 00:00:00 2001 From: JeremyStarTM Date: Wed, 21 Aug 2024 01:40:45 +0200 Subject: [PATCH] Add shutdown hook functionality (broken, #6) Doesn't really work right now, will likely be removed --- .../staropensource/sosengine/base/Engine.java | 111 ++++++++++++++--- .../sosengine/base/EngineInternals.java | 117 ++++++++++++++++++ .../sosengine/base/logging/Logger.java | 5 +- .../base/types/InternalAccessArea.java | 46 +++++++ 4 files changed, 258 insertions(+), 21 deletions(-) create mode 100644 base/src/main/java/de/staropensource/sosengine/base/EngineInternals.java create mode 100644 base/src/main/java/de/staropensource/sosengine/base/types/InternalAccessArea.java 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 bb2c8bfe..e159e406 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/Engine.java +++ b/base/src/main/java/de/staropensource/sosengine/base/Engine.java @@ -32,6 +32,7 @@ import de.staropensource.sosengine.base.exceptions.dependency.UnmetDependenciesE import de.staropensource.sosengine.base.internal.events.InternalEngineShutdownEvent; import de.staropensource.sosengine.base.internal.types.DependencySubsystemVector; import de.staropensource.sosengine.base.logging.CrashHandler; +import de.staropensource.sosengine.base.logging.InitLogger; import de.staropensource.sosengine.base.logging.Logger; import de.staropensource.sosengine.base.logging.LoggerInstance; import de.staropensource.sosengine.base.types.DependencyVector; @@ -40,6 +41,7 @@ import de.staropensource.sosengine.base.types.immutable.ImmutableLinkedList; import de.staropensource.sosengine.base.utility.DependencyResolver; import de.staropensource.sosengine.base.utility.Miscellaneous; import de.staropensource.sosengine.base.utility.PlaceholderEngine; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import org.jetbrains.annotations.NotNull; @@ -107,6 +109,21 @@ public final class Engine extends SubsystemClass { @Getter private @NotNull EngineState state = EngineState.UNKNOWN; + /** + * Contains a list of all registered subsystems. + * The list is sorted after initialization order. + * + * @since v1-alpha1 + * -- GETTER -- + * Returns a list of all registered subsystems. + * The list is sorted after initialization order. + * + * @return subsystem list + * @since v1-alpha1 + */ + @Getter + private @NotNull ImmutableLinkedList<@NotNull DependencySubsystemVector> subsystems = new ImmutableLinkedList<>(); + /** * Contains the engine's shutdown handler. * The shutdown handler is responsible for @@ -136,19 +153,39 @@ public final class Engine extends SubsystemClass { private @NotNull ShutdownHandler shutdownHandler = new Engine.JvmShutdownHandler(); /** - * Contains a list of all registered subsystems. - * The list is sorted after initialization order. + * Contains the JVM shutdown hook thread, + * which ensures that the engine is fully shut + * down before the JVM exits. * - * @since v1-alpha1 - * -- GETTER -- - * Returns a list of all registered subsystems. - * The list is sorted after initialization order. - * - * @return subsystem list - * @since v1-alpha1 + * @see EngineInternals#installSafetyShutdownHook(boolean) + * @since v1-alpha4 */ - @Getter - private @NotNull ImmutableLinkedList<@NotNull DependencySubsystemVector> subsystems = new ImmutableLinkedList<>(); + @Getter(AccessLevel.MODULE) + private final @NotNull Thread safetyShutdownHook = Thread.ofPlatform() + .name("Engine shutdown thread") + .group(getThreadGroup()) + .unstarted(() -> { + // To avoid race condition or something + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) {} + + // Check if already shutting down + switch (state) { + case UNKNOWN, SHUTDOWN, CRASHED -> { + return; + } + } + + // Print warning about shutdown + logger.warn("Trying to shut down engine using shutdown hook.\nThis approach to shutting down the engine and JVM is NOT RECOMMENDED, please use Engine#shutdown() instead."); + + // Shutdown + Engine.getInstance().shutdown(); + + // Print last message + InitLogger.warn(getClass(), "ENGINE", EngineInformation.getVersioningCodename(), "Engine successfully shut down using shutdown hook. PLEASE USE Engine#shutdown() INSTEAD OF System#exit() or Runtime#exit()!"); + }); /** * Initializes the StarOpenSource Engine. @@ -170,8 +207,9 @@ public final class Engine extends SubsystemClass { logger.info("Initializing engine"); initializeClasses(); // Initialize classes - if (checkEnvironment()) + if (checkEnvironment()) // Check environment throw new IllegalStateException("Running in an incompatible environment"); + ensureEnvironment(); // Prepare the environment and ensure safety populateCrashContent(); // Populate crash content cacheEvents(); // Cache event listeners startThreads(); // Start threads @@ -197,6 +235,19 @@ public final class Engine extends SubsystemClass { logger.info("Initialized sos!engine %engine_version% (commit %engine_git_commit_id_long%-%engine_git_branch%, dirty %engine_git_dirty%) in " + initTime + "ms"); } + /** + * Initializes all classes. + * + * @since v1-alpha0 + */ + private void initializeClasses() { + logger.verb("Initializing engine classes"); + new EngineInternals(); + new PlaceholderEngine(); + + EngineInformation.update(); + } + /** * Checks if the environment is compatible with the engine build. * @@ -212,15 +263,12 @@ public final class Engine extends SubsystemClass { } /** - * Initializes all classes. + * Ensures the execution safety of the environment. * - * @since v1-alpha0 + * @since v1-alpha4 */ - private void initializeClasses() { - logger.verb("Initializing engine classes"); - new PlaceholderEngine(); - - EngineInformation.update(); + private void ensureEnvironment() { + EngineInternals.getInstance().installSafetyShutdownHook(true); } /** @@ -412,6 +460,12 @@ public final class Engine extends SubsystemClass { * @since v1-alpha0 */ public synchronized void shutdown(@Range(from = 0, to = 255) int exitCode) { + switch (state) { + case UNKNOWN, SHUTDOWN, CRASHED -> { + return; + } + } + logger.info("Shutting engine down"); if (state != EngineState.CRASHED) state = EngineState.SHUTDOWN; @@ -425,12 +479,19 @@ public final class Engine extends SubsystemClass { // Flush log messages Logger.flushLogMessages(); + // Disable safety shutdown hook + try { + Runtime.getRuntime().removeShutdownHook(safetyShutdownHook); + } catch (Exception ignored) {} + + // Send events logger.verb("Notifiying classes about shutdown"); new EngineShutdownEvent().callEvent(); logger.verb("Notifying subsystems about shutdown"); new InternalEngineShutdownEvent().callEvent(); + // Invoke shutdown handler logger.verb("Invoking shutdown handler (code " + exitCode + ")"); shutdownHandler.shutdown((short) exitCode); } @@ -497,8 +558,18 @@ public final class Engine extends SubsystemClass { /** {@inheritDoc} */ @Override public void shutdown(short exitCode) { - logger.warn("Terminating JVM"); + // Check if already shutting down + try { + Thread thread = Thread.ofVirtual().unstarted(() -> {}); + Runtime.getRuntime().addShutdownHook(thread); + Runtime.getRuntime().removeShutdownHook(thread); + } catch (IllegalStateException exception) { + logger.warn("Terminating JVM: Already shutting down, skipping"); + return; + } + + logger.warn("Terminating JVM"); System.exit(exitCode); } } diff --git a/base/src/main/java/de/staropensource/sosengine/base/EngineInternals.java b/base/src/main/java/de/staropensource/sosengine/base/EngineInternals.java new file mode 100644 index 00000000..552ae80b --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/EngineInternals.java @@ -0,0 +1,117 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Contributors + * 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; + +import de.staropensource.sosengine.base.exceptions.NoAccessException; +import de.staropensource.sosengine.base.logging.LoggerInstance; +import de.staropensource.sosengine.base.types.InternalAccessArea; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class which allows access to the internals + * and changing the behaviour of the engine. + * + * @since v1-alpha4 + */ +@SuppressWarnings({ "JavadocDeclaration" }) +public final class EngineInternals { + /** + * Contains the class instance. + * + * @since v1-alpha4 + * -- GETTER -- + * Returns the class instance. + * + * @return class instance unless the engine is uninitialized + * @since v1-alpha4 + */ + @Getter + private static EngineInternals instance; + + /** + * Contains the {@link LoggerInstance} for this instance. + * + * @see LoggerInstance + * @since v1-alpha4 + */ + private static final LoggerInstance logger = new LoggerInstance.Builder().setClazz(EngineInternals.class).setOrigin("ENGINE").build(); + + /** + * Contains all disabled internal access areas. + * + * @since v1-alpha4 + */ + @Getter + private final @NotNull List<@NotNull InternalAccessArea> restrictedAreas = new ArrayList<>(); + + /** + * Constructs this class. + * + * @since v1-alpha4 + */ + public EngineInternals() { + // Only allow one instance + if (instance == null && Engine.getInstance() != null) + instance = this; + else + logger.crash("Only one instance of this class is allowed, use getInstance() instead of creating a new instance"); + } + + /** + * Allows disabling internal engine access. + * + * @since v1-alpha4 + */ + public void restrictAccess(@NotNull InternalAccessArea area) { + if (area == InternalAccessArea.ALL) { + List<@NotNull InternalAccessArea> areas = new ArrayList<>(List.of(InternalAccessArea.values())); + areas.remove(InternalAccessArea.ALL); + restrictedAreas.addAll(areas); + } + + restrictedAreas.add(area); + } + + /** + * Installs or uninstalls the JVM shutdown + * hook, which prevents the JVM from exiting + * before the engine has fully shut down. + * Highly recommended to keep enabled. + * + * @param status {@code true} to install, {@code false} otherwise + * @throws NoAccessException when restricted + * @since v1-alpha4 + */ + public void installSafetyShutdownHook(boolean status) throws NoAccessException { + if (restrictedAreas.contains(InternalAccessArea.SAFETY_SHUTDOWN_HOOK)) + throw new NoAccessException("The internal access area SAFETY_SHUTDOWN_HOOK has been restricted"); + + try { + if (status) + Runtime.getRuntime().addShutdownHook(Engine.getInstance().getSafetyShutdownHook()); + else + Runtime.getRuntime().removeShutdownHook(Engine.getInstance().getSafetyShutdownHook()); + } catch (IllegalArgumentException | IllegalStateException ignored) {} + } +} 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 07e67d1f..e548ba9e 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 @@ -200,6 +200,9 @@ public final class Logger { if (format == null) format = EngineConfiguration.getInstance().getLoggerTemplate(); + // Replace placeholders using PlaceholderEngine + format = PlaceholderEngine.getInstance().process(format); + // Replace logger placeholders (no colors) format = new LogClass(issuerClass).replace(format); format = new de.staropensource.sosengine.base.internal.placeholders.logger.LogLevel(level).replace(format); @@ -256,7 +259,7 @@ public final class Logger { format = new LogColorPrimary(level).replace(format); format = new LogColorSecondary(level).replace(format); - // Replace placeholders using PlaceholderEngine + // Replace placeholders using PlaceholderEngine again format = PlaceholderEngine.getInstance().process(format); // Invoke LoggerImpl#postPlaceholder diff --git a/base/src/main/java/de/staropensource/sosengine/base/types/InternalAccessArea.java b/base/src/main/java/de/staropensource/sosengine/base/types/InternalAccessArea.java new file mode 100644 index 00000000..56726ba0 --- /dev/null +++ b/base/src/main/java/de/staropensource/sosengine/base/types/InternalAccessArea.java @@ -0,0 +1,46 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Contributors + * 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.types; + +import de.staropensource.sosengine.base.EngineInternals; + +/** + * Specifies multiple areas of internal engine access. + * Used for restricting access of internals. + * + * @see EngineInternals#restrictAccess(InternalAccessArea) + * @since v1-alpha4 + */ +public enum InternalAccessArea { + /** + * Refers to all areas. + * + * @since v1-alpha4 + */ + ALL, + + /** + * Refers to the toggling of the JVM shutdown hook, which + * prevents JVM shutdowns without the engine first shutting down. + * + * @since v1-alpha4 + */ + SAFETY_SHUTDOWN_HOOK, +}