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.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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
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
|
||||
|
|
|
@ -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