Overhaul engine init procedure + add new placeholder

This commit is contained in:
JeremyStar™ 2024-11-05 21:13:30 +01:00
parent 4281f946be
commit c533a06148
Signed by: JeremyStarTM
GPG key ID: E366BAEF67E4704D
8 changed files with 200 additions and 135 deletions

View file

@ -32,6 +32,7 @@ import de.staropensource.engine.base.internal.type.DependencySubsystemVector;
import de.staropensource.engine.base.logging.PrintStreamService;
import de.staropensource.engine.base.logging.*;
import de.staropensource.engine.base.logging.backend.async.LoggingQueue;
import de.staropensource.engine.base.logging.backend.async.LoggingThread;
import de.staropensource.engine.base.type.DependencyVector;
import de.staropensource.engine.base.type.EngineState;
import de.staropensource.engine.base.type.immutable.ImmutableLinkedList;
@ -51,7 +52,6 @@ import org.reflections.scanners.Scanners;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import java.io.IOException;
import java.util.*;
/**
@ -177,49 +177,104 @@ public final class Engine extends SubsystemClass {
/**
* Initializes the StarOpenSource Engine.
*
* @throws IllegalStateException when running in an incompatible environment
* @since v1-alpha6
* @throws RuntimeException for all exceptions thrown by this constructor
* @since v1-alpha8
*/
private Engine() throws RuntimeException {
instance = this;
state = EngineState.EARLY_STARTUP;
try {
instance = this;
state = EngineState.EARLY_STARTUP;
long initTime = Miscellaneous.measureExecutionTime(() -> {
try {
de.staropensource.engine.base.logging.backend.async.LoggingThread.startThread(false);
// For measuring the initialization time
long initTimeEarly = System.currentTimeMillis();
long initTimeLate = initTimeEarly;
new EngineConfiguration();
EngineConfiguration.getInstance().loadConfiguration();
// Check for incompatible JVM implementations
checkJvmIncompatibilities();
Logger.info("Initializing engine");
initializeClasses(); // Initialize classes
if (checkEnvironment()) // Check environment
throw new IllegalStateException("Running in an incompatible environment");
ensureEnvironment(); // Prepare the environment and ensure safety
cacheEvents(); // Cache event listeners
// Display that the engine is initializing
Logger.verb("Initializing engine");
Logger.verb("Completing early initialization stage");
state = EngineState.STARTUP;
// Start the logging thread
Logger.diag("Starting logging infrastructure");
LoggingThread.startThread(false);
PrintStreamService.initializeStreams();
// Perform automatic subsystem initialization
if (EngineConfiguration.getInstance().isInitialPerformSubsystemInitialization()) {
collectSubsystems(); // Collect subsystems
// Initialize EngineInternals
Logger.diag("Initializing EngineInternals class");
new EngineInternals();
// Initialize subsystems
try {
initializeSubsystems();
} catch (Exception exception) {
Logger.error("Subsystem dependency resolution failed");
}
// Load engine configuration
Logger.diag("Loading engine configuration");
new EngineConfiguration();
EngineConfiguration.getInstance().loadConfiguration();
// Load engine build information
Logger.diag("Loading engine build information");
EngineInformation.update();
// Check for reflective classpath scanning compatibility
checkReflectiveClasspathScanningCompatibility();
// Check for Java version incompatibilities
checkJavaVersion();
// Initialize PlaceholderEngine
Logger.diag("Initializing PlaceholderEngine");
PlaceholderEngine.initialize();
// Initialize static FileAccess instances
Logger.diag("Initializing static FileAccess instances");
FileAccess.initializeInstances();
// Install the safety shutdown hook
Logger.diag("Installing safety shutdown hook");
EngineInternals.getInstance().installSafetyShutdownHook(true);
// Cache events
Logger.diag("Caching event listeners");
cacheEvents();
// Complete early initialization stage
Logger.verb("Completing early initialization stage");
state = EngineState.STARTUP;
initTimeEarly = System.currentTimeMillis() - initTimeEarly;
// Perform automatic subsystem initialization
if (EngineConfiguration.getInstance().isInitialPerformSubsystemInitialization()) {
// Collect all subsystems
Logger.diag("Collecting subsystems");
collectSubsystems();
// Initialize subsystems
try {
initializeSubsystems();
} catch (Exception exception) {
Logger.error("Subsystem dependency resolution failed");
}
} catch (Exception exception) {
throw new RuntimeException(exception);
}
});
Logger.verb("Completing late initialization stage");
state = EngineState.RUNNING;
Logger.info("Initialized sos!engine %engine_version% (commit %engine_git_commit_id_long%-%engine_git_branch%, dirty %engine_git_dirty%) in " + initTime + "ms\nThe StarOpenSource Engine is licensed under the GNU AGPL v3. Copyright (c) 2024 The StarOpenSource Engine Authors.");
// Complete late initialization stage
Logger.verb("Completing late initialization stage");
state = EngineState.RUNNING;
initTimeLate = System.currentTimeMillis() - initTimeLate;
// Print welcome message
Logger.info(
"""
Welcome to the StarOpenSource Engine "%engine_version_codename%" %engine_version%!
Running commit %engine_git_commit_id_long% (dirty %engine_git_dirty%).
Initialization took %init_time_total%ms (early %init_time_early%ms, late %init_time_late%ms).
Copyright (c) 2024 The StarOpenSource Engine Authors
Licensed under the GNU Affero General Public License v3"""
.replace("%init_time_total%", String.valueOf(initTimeEarly + initTimeLate))
.replace("%init_time_early%", String.valueOf(initTimeEarly))
.replace("%init_time_late%", String.valueOf(initTimeLate))
);
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}
/**
@ -230,54 +285,32 @@ public final class Engine extends SubsystemClass {
* @throws RuntimeException on engine initialization failure
* @since v1-alpha6
*/
public static void initialize() throws IllegalStateException, RuntimeException {
public static void initialize() throws RuntimeException {
try {
if (instance == null)
new Engine();
} catch (RuntimeException exception) {
if (exception.getCause() instanceof IllegalStateException)
throw (IllegalStateException) exception.getCause();
else {
Logger.error("Engine initialization failed");
Logger.error(Miscellaneous.getStackTraceHeader(exception.getCause()));
for (String line : Miscellaneous.getStackTraceAsString(exception.getCause(), true).split("\n"))
Logger.error(line);
throw new RuntimeException("Engine initialization failed", exception.getCause());
}
Logger.error("Engine initialization failed");
Logger.error(Miscellaneous.getStackTraceHeader(exception.getCause()));
for (String line : Miscellaneous.getStackTraceAsString(exception.getCause(), true).split("\n"))
Logger.error(line);
throw new RuntimeException("Engine initialization failed", exception.getCause());
}
}
/**
* Initializes all classes.
* Checks if the running JVM implementation is not supported by the engine.
*
* @since v1-alpha0
* @since v1-alpha8
*/
private void initializeClasses() throws IOException {
Logger.verb("Initializing engine classes");
private void checkJvmIncompatibilities() {
if (System.getProperties().getProperty("sosengine.base.allowUnsupportedJVMInitialization", "false").equals("true")) {
Logger.warn("Skipping JVM implementation incompatibilities check");
return;
}
// Initialize essential engine classes
new EngineInternals();
EngineInformation.update();
PlaceholderEngine.initialize();
// Non-essential engine classes
PrintStreamService.initializeStreams();
FileAccess.initializeInstances();
}
/**
* Checks if the environment is compatible with the engine build.
*
* @since v1-alpha4
*/
private boolean checkEnvironment() {
Logger.diag("Checking environment");
// Warn about potential Java incompatibilities
if (JvmInformation.getJavaVersion() > EngineInformation.getJavaSource())
Logger.warn("The StarOpenSource Engine is running on an untested Java version.\nThings may not work as expected or features which can improve performance, stability, compatibility or ease of use may be missing.\nIf you encounter issues, try running a JVM implementing Java " + EngineInformation.getJavaSource());
// Shutdown if running in an unsupported JVM
// Substrate VM (GraalVM Community)
if (JvmInformation.getImplementationName().equals("Substrate VM") && JvmInformation.getImplementationVendor().equals("GraalVM Community")) {
Logger.error("##############################################################################################");
Logger.error("## Running in Substrate VM, which is the name of the JVM used by GraalVM native-image. ##");
@ -293,37 +326,33 @@ public final class Engine extends SubsystemClass {
Logger.error("##############################################################################################");
Runtime.getRuntime().exit(255);
}
}
/**
* Checks if reflective classpath scanning is supported.
*
* @since v1-alpha8
*/
private void checkReflectiveClasspathScanningCompatibility() {
// Check if reflective classpath scanning is supported
if (checkClasspathScanningSupport()) {
Logger.warn("Running in an classpath scanning-unfriendly environment, disabling.");
if (System.getProperties().getProperty("sosengine.base.considerEnvironmentUnfriendlyToClasspathScanning", "false").equals("true")) {
Logger.warn("Running in an classpath scanning-unfriendly environment, disabling classpath scanning support.");
Logger.warn("If shit doesn't work and is expected to be discovered by annotations, you'll need to");
Logger.warn("either register it first or have to place classes in some package.");
Logger.warn("either register it first or have to update some engine configuration setting.");
Logger.warn("Please consult sos!engine's documentation for more information about this issue.");
EngineInternals.getInstance().overrideReflectiveClasspathScanning(false);
}
return false;
}
/**
* Returns whether scanning the classpath is supported.
* Checks and warns if the Java version of the
* running JVM is higher than the engine supports.
*
* @return test results
* @since v1-alpha5
* @since v1-alpha8
*/
private boolean checkClasspathScanningSupport() {
// This may be expanded in the future
return EngineConfiguration.getInstance().isInitialForceDisableClasspathScanning();
}
/**
* Ensures the execution safety of the environment.
*
* @since v1-alpha4
*/
private void ensureEnvironment() {
EngineInternals.getInstance().installSafetyShutdownHook(true);
private void checkJavaVersion() {
if (JvmInformation.getJavaVersion() > EngineInformation.getJavaSource())
Logger.warn("The StarOpenSource Engine is running on an untested Java version.\nThings may not work as expected or features which can improve performance, stability, compatibility or ease of use may be missing.\nIf you encounter issues, try running a JVM implementing Java " + EngineInformation.getJavaSource());
}
/**
@ -332,15 +361,10 @@ public final class Engine extends SubsystemClass {
* @since v1-alpha0
*/
private void cacheEvents() {
Logger.diag("Caching events");
// Internal events
EventHelper.cacheEvent(InternalEngineShutdownEvent.class);
// General events
EventHelper.cacheEvent(EngineCrashEvent.class);
EventHelper.cacheEvent(EngineShutdownEvent.class);
EventHelper.cacheEvent(EngineSoftCrashEvent.class);
EventHelper.cacheEvent(InternalEngineShutdownEvent.class);
EventHelper.cacheEvent(LogEvent.class);
EventHelper.cacheEvent(ThrowableCatchEvent.class);
}
@ -427,7 +451,7 @@ public final class Engine extends SubsystemClass {
resolver.addVectors(subsystems);
// Resolve dependencies and get order
Logger.verb("Resolving subsystem dependencies");
Logger.diag("Resolving subsystem dependencies");
try {
for (DependencyVector vector : resolver.resolve().getOrder()) // smol workaround
order.add((DependencySubsystemVector) vector);
@ -450,7 +474,7 @@ public final class Engine extends SubsystemClass {
}
// Initialize subsystems
Logger.verb("Initializing engine subsystems");
Logger.diag("Initializing engine subsystems");
long initTime;
for (DependencySubsystemVector vector : subsystems) {
Logger.diag("Initializing subsystem '" + vector.getSubsystemClass().getName() + "' (" + vector.getSubsystemClass().getClass().getName() + ")");

View file

@ -128,21 +128,6 @@ public final class EngineConfiguration extends Configuration {
*/
private boolean initialPerformSubsystemInitialization;
/**
* If enabled, will force-disable reflective classpath scanning.
*
* @see EngineInternals#getReflectiveClasspathScanning()
* @see EngineInternals#overrideReflectiveClasspathScanning(boolean)
* @since v1-alpha5
* -- GETTER --
* Gets the value for {@link #initialForceDisableClasspathScanning}.
*
* @return variable value
* @see #initialForceDisableClasspathScanning
* @since v1-alpha5
*/
private boolean initialForceDisableClasspathScanning;
/**
* Will try to load the specified classes as subsystems,
* if reflective classpath scanning is disabled.
@ -352,7 +337,6 @@ public final class EngineConfiguration extends Configuration {
case "debugEvents" -> debugEvents = parser.getBoolean(group + property);
case "initialPerformSubsystemInitialization" -> initialPerformSubsystemInitialization = parser.getBoolean(group + property);
case "initialForceDisableClasspathScanning" -> initialForceDisableClasspathScanning = parser.getBoolean(group + property);
case "initialIncludeSubsystemClasses" -> {
initialIncludeSubsystemClasses = new HashSet<>();
initialIncludeSubsystemClasses.addAll(Arrays.stream(parser.getString(group + property).split(",")).toList());
@ -374,7 +358,7 @@ public final class EngineConfiguration extends Configuration {
try {
logLevel = LogLevel.valueOf(parser.getString(group + property).toUpperCase());
} catch (IllegalArgumentException ignored) {
Logger.error("The log level " + parser.getString(group + property) + " is not valid");
Logger.error("The log level '" + parser.getString(group + property) + "' is not valid");
}
}
case "logFeatures" -> logFeatures = Set.copyOf(Arrays.stream(parser.getString(group + property).split(",")).toList());
@ -403,7 +387,6 @@ public final class EngineConfiguration extends Configuration {
debugEvents = false;
initialPerformSubsystemInitialization = true;
initialForceDisableClasspathScanning = false;
initialIncludeSubsystemClasses = new HashSet<>();
errorShortcodeParser = true;
@ -428,7 +411,6 @@ public final class EngineConfiguration extends Configuration {
case "debugEvents" -> debugEvents;
case "initialPerformSubsystemInitialization" -> initialPerformSubsystemInitialization;
case "initialForceDisableClasspathScanning" -> initialForceDisableClasspathScanning;
case "initialIncludeSubsystemClasses" -> initialIncludeSubsystemClasses;
case "errorShortcodeParser" -> errorShortcodeParser;

View file

@ -0,0 +1,46 @@
/*
* STAROPENSOURCE ENGINE SOURCE FILE
* Copyright (c) 2024 The StarOpenSource Engine Authors
* 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.engine.base.internal.implementation.placeholder;
import de.staropensource.engine.base.implementable.Placeholder;
import de.staropensource.engine.base.utility.information.EngineInformation;
import org.jetbrains.annotations.NotNull;
/**
* Implements the {@code engine_version_codename} placeholder.
*
* @see Placeholder
* @since v1-alpha8
*/
@SuppressWarnings({ "unused" })
public final class EngineVersionCodename implements Placeholder {
/**
* Creates and initializes an instance of this event.
*
* @since v1-alpha8
*/
public EngineVersionCodename() {}
/** {@inheritDoc} */
@Override
public @NotNull String replace(@NotNull String text) {
return text.replace("%engine_version_codename%", EngineInformation.getVersioningCodename());
}
}

View file

@ -19,6 +19,8 @@
package de.staropensource.engine.base.logging;
import de.staropensource.engine.base.Engine;
import de.staropensource.engine.base.type.EngineState;
import de.staropensource.engine.base.type.logging.LogLevel;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
@ -164,18 +166,14 @@ public final class PrintStreamService {
/**
* Initializes all {@link PrintStream}s offered by this class.
* <p>
* Only works during early engine startup.
*
* @since v1-alpha4
* @since v1-alpha8
*/
public static void initializeStreams() {
// Close all existing streams
if (diag != null) diag.close();
if (verb != null) verb.close();
if (sarn != null) sarn.close();
if (info != null) info.close();
if (warn != null) warn.close();
if (error != null) error.close();
if (crash != null) crash.close();
if (Engine.getInstance() == null || Engine.getInstance().getState() != EngineState.EARLY_STARTUP)
return;
// Create streams
diag = LogStream.createPrintStream(LogLevel.DIAGNOSTIC);

View file

@ -222,10 +222,6 @@ public final class CrashHandler {
.append(EngineConfiguration.getInstance().isInitialPerformSubsystemInitialization())
.append("'\n")
.append("EngineConfiguration#initialForceDisableClasspathScanning='")
.append(EngineConfiguration.getInstance().isInitialForceDisableClasspathScanning())
.append("'\n")
.append("EngineConfiguration#initialIncludeSubsystemClasses='")
.append(EngineConfiguration.getInstance().getInitialIncludeSubsystemClasses())
.append("'\n")

View file

@ -53,7 +53,7 @@ public final class Processor {
* @since v1-alpha8
*/
public static boolean isFeatureEnabled(@NotNull String feature) {
return EngineConfiguration.getInstance().getLogFeatures().contains(feature);
return EngineConfiguration.getInstance() != null && EngineConfiguration.getInstance().getLogFeatures().contains(feature);
}
/**
@ -66,7 +66,7 @@ public final class Processor {
* @since v1-alpha8
*/
public static void handle(@NotNull LogLevel level, @NotNull StackTraceElement issuer, @NotNull String message) {
if (EngineConfiguration.getInstance().isOptimizeLogging())
if (EngineConfiguration.getInstance() != null && EngineConfiguration.getInstance().isOptimizeLogging())
LoggingQueue.add(level, issuer, message);
else
process(level, issuer, message);
@ -84,7 +84,18 @@ public final class Processor {
StringBuilder output = new StringBuilder();
// Filter out
if (level.compareTo(EngineConfiguration.getInstance().getLogLevel()) < 0)
if (EngineConfiguration.getInstance() == null) {
LogLevel maxLevel = LogLevel.INFORMATIONAL;
try {
maxLevel = LogLevel.valueOf(System.getProperties().getProperty("sosengine.base.logLevel", "informational").toUpperCase());
} catch (IllegalArgumentException ignored) {
Logger.error("The log level '" + System.getProperties().getProperty("sosengine.base.logLevel", "informational") + "' is not valid");
}
if (level.compareTo(maxLevel) < 0)
return;
} else if (level.compareTo(EngineConfiguration.getInstance().getLogLevel()) < 0)
return;
for (String classNameDisallowed : Filterer.disallowedClasses)

View file

@ -44,18 +44,25 @@ public final class LoggingThread {
* @since v1-alpha8
*/
private static final @NotNull Runnable threadCode = () -> {
int pollingSpeed;
while (!(
Thread.currentThread().isInterrupted()
|| !(EngineConfiguration.getInstance() == null || EngineConfiguration.getInstance().isOptimizeLogging())
|| Engine.getInstance().getState() == EngineState.SHUTDOWN
|| Engine.getInstance().getState() == EngineState.CRASHED
)) {
if (EngineConfiguration.getInstance() == null)
pollingSpeed = 5;
else
pollingSpeed = EngineConfiguration.getInstance().getLogPollingSpeed();
// Flush all log messages
LoggingQueue.flush();
// Sleep for whatever has been configured
if (EngineConfiguration.getInstance().getLogPollingSpeed() > 0) {
long sleepDuration = System.currentTimeMillis() + EngineConfiguration.getInstance().getLogPollingSpeed();
if (pollingSpeed > 0) {
long sleepDuration = System.currentTimeMillis() + pollingSpeed;
while (System.currentTimeMillis() < sleepDuration)
Thread.onSpinWait();
}

View file

@ -97,6 +97,7 @@ public final class PlaceholderEngine {
placeholders.add(new EngineGitDirty());
// engine_version*
placeholders.add(new EngineVersion());
placeholders.add(new EngineVersionCodename());
placeholders.add(new EngineVersionVersion());
placeholders.add(new EngineVersionType());
placeholders.add(new EngineVersionTyperelease());