Add shutdown hook functionality (broken, #6)
Doesn't really work right now, will likely be removed
This commit is contained in:
parent
5718f33d88
commit
2fedc981af
4 changed files with 258 additions and 21 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
Loading…
Reference in a new issue