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 939b5fa..e159e40 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; @@ -150,6 +152,41 @@ public final class Engine extends SubsystemClass { @Setter private @NotNull ShutdownHandler shutdownHandler = new Engine.JvmShutdownHandler(); + /** + * Contains the JVM shutdown hook thread, + * which ensures that the engine is fully shut + * down before the JVM exits. + * + * @see EngineInternals#installSafetyShutdownHook(boolean) + * @since v1-alpha4 + */ + @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. * @@ -172,6 +209,7 @@ public final class Engine extends SubsystemClass { initializeClasses(); // Initialize classes 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 @@ -224,6 +262,15 @@ public final class Engine extends SubsystemClass { return false; } + /** + * Ensures the execution safety of the environment. + * + * @since v1-alpha4 + */ + private void ensureEnvironment() { + EngineInternals.getInstance().installSafetyShutdownHook(true); + } + /** * This method populates {@link CrashHandler#crashContent} with content. * @@ -432,6 +479,11 @@ 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(); diff --git a/base/src/main/java/de/staropensource/sosengine/base/EngineInternals.java b/base/src/main/java/de/staropensource/sosengine/base/EngineInternals.java index 674892b..552ae80 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/EngineInternals.java +++ b/base/src/main/java/de/staropensource/sosengine/base/EngineInternals.java @@ -19,6 +19,7 @@ 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; @@ -91,4 +92,26 @@ public final class EngineInternals { 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/types/InternalAccessArea.java b/base/src/main/java/de/staropensource/sosengine/base/types/InternalAccessArea.java index 653cd78..56726ba 100644 --- a/base/src/main/java/de/staropensource/sosengine/base/types/InternalAccessArea.java +++ b/base/src/main/java/de/staropensource/sosengine/base/types/InternalAccessArea.java @@ -35,4 +35,12 @@ public enum InternalAccessArea { * @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, }