Add shutdown hook functionality (broken, #6)
Some checks failed
build-and-test / generate-javadoc (push) Failing after 1m54s
build-and-test / build (push) Failing after 2m6s
build-and-test / test (push) Failing after 2m26s

Doesn't really work right now, will likely be removed
This commit is contained in:
JeremyStar™ 2024-08-21 01:40:45 +02:00
parent 5718f33d88
commit 2fedc981af
Signed by: JeremyStarTM
GPG key ID: E366BAEF67E4704D
4 changed files with 258 additions and 21 deletions

View file

@ -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.events.InternalEngineShutdownEvent;
import de.staropensource.sosengine.base.internal.types.DependencySubsystemVector; import de.staropensource.sosengine.base.internal.types.DependencySubsystemVector;
import de.staropensource.sosengine.base.logging.CrashHandler; 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.Logger;
import de.staropensource.sosengine.base.logging.LoggerInstance; import de.staropensource.sosengine.base.logging.LoggerInstance;
import de.staropensource.sosengine.base.types.DependencyVector; 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.DependencyResolver;
import de.staropensource.sosengine.base.utility.Miscellaneous; import de.staropensource.sosengine.base.utility.Miscellaneous;
import de.staropensource.sosengine.base.utility.PlaceholderEngine; import de.staropensource.sosengine.base.utility.PlaceholderEngine;
import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -107,6 +109,21 @@ public final class Engine extends SubsystemClass {
@Getter @Getter
private @NotNull EngineState state = EngineState.UNKNOWN; 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. * Contains the engine's shutdown handler.
* The shutdown handler is responsible for * The shutdown handler is responsible for
@ -136,19 +153,39 @@ public final class Engine extends SubsystemClass {
private @NotNull ShutdownHandler shutdownHandler = new Engine.JvmShutdownHandler(); private @NotNull ShutdownHandler shutdownHandler = new Engine.JvmShutdownHandler();
/** /**
* Contains a list of all registered subsystems. * Contains the JVM shutdown hook thread,
* The list is sorted after initialization order. * which ensures that the engine is fully shut
* down before the JVM exits.
* *
* @since v1-alpha1 * @see EngineInternals#installSafetyShutdownHook(boolean)
* -- GETTER -- * @since v1-alpha4
* Returns a list of all registered subsystems.
* The list is sorted after initialization order.
*
* @return subsystem list
* @since v1-alpha1
*/ */
@Getter @Getter(AccessLevel.MODULE)
private @NotNull ImmutableLinkedList<@NotNull DependencySubsystemVector> subsystems = new ImmutableLinkedList<>(); 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. * Initializes the StarOpenSource Engine.
@ -170,8 +207,9 @@ public final class Engine extends SubsystemClass {
logger.info("Initializing engine"); logger.info("Initializing engine");
initializeClasses(); // Initialize classes initializeClasses(); // Initialize classes
if (checkEnvironment()) if (checkEnvironment()) // Check environment
throw new IllegalStateException("Running in an incompatible environment"); throw new IllegalStateException("Running in an incompatible environment");
ensureEnvironment(); // Prepare the environment and ensure safety
populateCrashContent(); // Populate crash content populateCrashContent(); // Populate crash content
cacheEvents(); // Cache event listeners cacheEvents(); // Cache event listeners
startThreads(); // Start threads 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"); 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. * 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() { private void ensureEnvironment() {
logger.verb("Initializing engine classes"); EngineInternals.getInstance().installSafetyShutdownHook(true);
new PlaceholderEngine();
EngineInformation.update();
} }
/** /**
@ -412,6 +460,12 @@ public final class Engine extends SubsystemClass {
* @since v1-alpha0 * @since v1-alpha0
*/ */
public synchronized void shutdown(@Range(from = 0, to = 255) int exitCode) { public synchronized void shutdown(@Range(from = 0, to = 255) int exitCode) {
switch (state) {
case UNKNOWN, SHUTDOWN, CRASHED -> {
return;
}
}
logger.info("Shutting engine down"); logger.info("Shutting engine down");
if (state != EngineState.CRASHED) if (state != EngineState.CRASHED)
state = EngineState.SHUTDOWN; state = EngineState.SHUTDOWN;
@ -425,12 +479,19 @@ public final class Engine extends SubsystemClass {
// Flush log messages // Flush log messages
Logger.flushLogMessages(); Logger.flushLogMessages();
// Disable safety shutdown hook
try {
Runtime.getRuntime().removeShutdownHook(safetyShutdownHook);
} catch (Exception ignored) {}
// Send events
logger.verb("Notifiying classes about shutdown"); logger.verb("Notifiying classes about shutdown");
new EngineShutdownEvent().callEvent(); new EngineShutdownEvent().callEvent();
logger.verb("Notifying subsystems about shutdown"); logger.verb("Notifying subsystems about shutdown");
new InternalEngineShutdownEvent().callEvent(); new InternalEngineShutdownEvent().callEvent();
// Invoke shutdown handler
logger.verb("Invoking shutdown handler (code " + exitCode + ")"); logger.verb("Invoking shutdown handler (code " + exitCode + ")");
shutdownHandler.shutdown((short) exitCode); shutdownHandler.shutdown((short) exitCode);
} }
@ -497,8 +558,18 @@ public final class Engine extends SubsystemClass {
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public void shutdown(short exitCode) { 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); System.exit(exitCode);
} }
} }

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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) {}
}
}

View file

@ -200,6 +200,9 @@ public final class Logger {
if (format == null) if (format == null)
format = EngineConfiguration.getInstance().getLoggerTemplate(); format = EngineConfiguration.getInstance().getLoggerTemplate();
// Replace placeholders using PlaceholderEngine
format = PlaceholderEngine.getInstance().process(format);
// Replace logger placeholders (no colors) // Replace logger placeholders (no colors)
format = new LogClass(issuerClass).replace(format); format = new LogClass(issuerClass).replace(format);
format = new de.staropensource.sosengine.base.internal.placeholders.logger.LogLevel(level).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 LogColorPrimary(level).replace(format);
format = new LogColorSecondary(level).replace(format); format = new LogColorSecondary(level).replace(format);
// Replace placeholders using PlaceholderEngine // Replace placeholders using PlaceholderEngine again
format = PlaceholderEngine.getInstance().process(format); format = PlaceholderEngine.getInstance().process(format);
// Invoke LoggerImpl#postPlaceholder // Invoke LoggerImpl#postPlaceholder

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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,
}