systeminformation thread not ending: move error handling from utils to app (#4160)

This commit is contained in:
Karsten Hassel
2026-05-20 00:03:15 +02:00
committed by GitHub
parent 3c514dff3a
commit 7abbe62cb0
3 changed files with 96 additions and 91 deletions
+76 -77
View File
@@ -14,6 +14,7 @@ const { setGlobalDispatcher, Agent } = require("undici");
const Server = require("./server");
const Utils = require("./utils");
const { ConfigError } = require("./utils");
const { getEnvVarsAsObj } = require("#server_functions");
// common timeout value, provide environment override in case
@@ -183,80 +184,92 @@ function App () {
* @returns {Promise<object>} the config used
*/
this.start = async function () {
const configObj = Utils.loadConfig();
global.config = configObj.fullConf;
const config = global.config;
Utils.checkConfigFile(configObj);
try {
const configObj = Utils.loadConfig();
global.config = configObj.fullConf;
const config = global.config;
Utils.checkConfigFile(configObj);
global.defaultModulesDir = config.defaultModulesDir;
defaultModules = require(`${global.root_path}/${global.defaultModulesDir}/defaultmodules`);
global.defaultModulesDir = config.defaultModulesDir;
defaultModules = require(`${global.root_path}/${global.defaultModulesDir}/defaultmodules`);
Log.setLogLevel(config.logLevel);
Log.setLogLevel(config.logLevel);
env = getEnvVarsAsObj();
// check for deprecated css/custom.css and move it to new location
if ((!fs.existsSync(`${global.root_path}/${env.customCss}`)) && (fs.existsSync(`${global.root_path}/css/custom.css`))) {
try {
fs.renameSync(`${global.root_path}/css/custom.css`, `${global.root_path}/${env.customCss}`);
Log.warn(`WARNING! Your custom css file was moved from ${global.root_path}/css/custom.css to ${global.root_path}/${env.customCss}`);
} catch {
Log.warn("WARNING! Your custom css file is currently located in the css folder. Please move it to the config folder!");
env = getEnvVarsAsObj();
// check for deprecated css/custom.css and move it to new location
if ((!fs.existsSync(`${global.root_path}/${env.customCss}`)) && (fs.existsSync(`${global.root_path}/css/custom.css`))) {
try {
fs.renameSync(`${global.root_path}/css/custom.css`, `${global.root_path}/${env.customCss}`);
Log.warn(`WARNING! Your custom css file was moved from ${global.root_path}/css/custom.css to ${global.root_path}/${env.customCss}`);
} catch {
Log.warn("WARNING! Your custom css file is currently located in the css folder. Please move it to the config folder!");
}
}
}
// get the used module positions
Utils.getModulePositions();
// get the used module positions
Utils.getModulePositions();
let modules = [];
for (const module of config.modules) {
if (module.disabled) continue;
if (module.module) {
if (Utils.moduleHasValidPosition(module.position) || typeof (module.position) === "undefined") {
// Only add this module to be loaded if it is not a duplicate (repeated instance of the same module)
if (!modules.includes(module.module)) {
modules.push(module.module);
let modules = [];
for (const module of config.modules) {
if (module.disabled) continue;
if (module.module) {
if (Utils.moduleHasValidPosition(module.position) || typeof (module.position) === "undefined") {
// Only add this module to be loaded if it is not a duplicate (repeated instance of the same module)
if (!modules.includes(module.module)) {
modules.push(module.module);
}
} else {
Log.warn("Invalid module position found for this configuration:" + `\n${JSON.stringify(module, null, 2)}`);
}
} else {
Log.warn("Invalid module position found for this configuration:" + `\n${JSON.stringify(module, null, 2)}`);
Log.warn("No module name found for this configuration:" + `\n${JSON.stringify(module, null, 2)}`);
}
} else {
Log.warn("No module name found for this configuration:" + `\n${JSON.stringify(module, null, 2)}`);
}
setGlobalDispatcher(new Agent({ connect: { timeout: fetch_timeout } }));
await loadModules(modules);
httpServer = new Server(configObj);
const { app, io } = await httpServer.open();
Log.log("Server started ...");
const nodePromises = [];
for (let nodeHelper of nodeHelpers) {
nodeHelper.setExpressApp(app);
nodeHelper.setSocketIO(io);
try {
nodePromises.push(nodeHelper.start());
} catch (error) {
Log.error(`Error when starting node_helper for module ${nodeHelper.name}:`);
Log.error(error);
}
}
const results = await Promise.allSettled(nodePromises);
// Log errors that happened during async node_helper startup
results.forEach((result) => {
if (result.status === "rejected") {
Log.error(result.reason);
}
});
Log.log("Sockets connected & modules started ...");
return global.config;
} catch (err) {
// planned ConfigErrors already logged their message before throwing
if (!(err instanceof ConfigError)) {
Log.error("Unexpected error during startup:", err);
}
const int32 = new Int32Array(new SharedArrayBuffer(4));
// wait 1000ms before exiting so that child processes (e.g. systeminformation) have some additional time
Atomics.wait(int32, 0, 0, 1000);
process.exit(1);
}
setGlobalDispatcher(new Agent({ connect: { timeout: fetch_timeout } }));
await loadModules(modules);
httpServer = new Server(configObj);
const { app, io } = await httpServer.open();
Log.log("Server started ...");
const nodePromises = [];
for (let nodeHelper of nodeHelpers) {
nodeHelper.setExpressApp(app);
nodeHelper.setSocketIO(io);
try {
nodePromises.push(nodeHelper.start());
} catch (error) {
Log.error(`Error when starting node_helper for module ${nodeHelper.name}:`);
Log.error(error);
}
}
const results = await Promise.allSettled(nodePromises);
// Log errors that happened during async node_helper startup
results.forEach((result) => {
if (result.status === "rejected") {
Log.error(result.reason);
}
});
Log.log("Sockets connected & modules started ...");
return global.config;
};
/**
@@ -328,20 +341,6 @@ function App () {
await this.stop();
process.exit(0);
});
/**
*
* @param {number} ms milliseconds to wait
*/
function blockingSleep (ms) {
const int32 = new Int32Array(new SharedArrayBuffer(4));
Atomics.wait(int32, 0, 0, ms);
}
process.on("exit", () => {
// wait before exiting so that child processes (e.g. systeminformation) have some additional time
blockingSleep(1000);
});
}
module.exports = new App();
+14 -8
View File
@@ -14,6 +14,13 @@ const { getConfigFilePath } = require("#server_functions");
const linter = new Linter({ configType: "flat" });
class ConfigError extends Error {
constructor (message) {
super(message);
this.name = "ConfigError";
}
}
const requireFromString = (src) => {
const m = new module.constructor();
m._compile(src, "");
@@ -172,9 +179,8 @@ const loadConfig = () => {
} else {
Log.error(`Cannot access config file: ${configFilename}\n${error.message}`);
}
process.exit(1);
throw new ConfigError("");
}
return {};
};
/**
@@ -220,7 +226,7 @@ const checkConfigFile = (configObject) => {
errorMessage += `\nLine ${error.line} column ${error.column}: ${error.message}`;
}
Log.error(errorMessage);
process.exit(1);
throw new ConfigError("");
}
};
@@ -242,7 +248,7 @@ const validateModulePositions = (data) => {
// `modules` always exists (defaults.js provides a default array), but guard against it being overridden with a non-array value
if (data.modules !== undefined && !Array.isArray(data.modules)) {
Log.error("This module configuration contains errors:\nmodules must be an array");
process.exit(1);
throw new ConfigError("");
}
// Validate each module entry
@@ -250,19 +256,19 @@ const validateModulePositions = (data) => {
// Each module entry must be an object so we can safely inspect its fields
if (mod === null || typeof mod !== "object" || Array.isArray(mod)) {
Log.error(`This module configuration contains errors:\n${JSON.stringify(mod, null, 2)}\nmodule entry must be an object`);
process.exit(1);
throw new ConfigError("");
}
// `module` (the module name) is required and must be a string
if (typeof mod.module !== "string") {
Log.error(`This module configuration contains errors:\n${JSON.stringify(mod, null, 2)}\nmodule: must be a string`);
process.exit(1);
throw new ConfigError("");
}
// `position` is optional, but must be a string when provided
if (mod.position !== undefined && typeof mod.position !== "string") {
Log.error(`This module configuration contains errors:\n${JSON.stringify(mod, null, 2)}\nposition: must be a string`);
process.exit(1);
throw new ConfigError("");
}
// `position` is optional, but when set it must match a known region
@@ -275,4 +281,4 @@ const validateModulePositions = (data) => {
Log.info(styleText("green", "Your modules structure configuration doesn't contain errors :)"));
};
module.exports = { loadConfig, getModulePositions, moduleHasValidPosition, getAvailableModulePositions, checkConfigFile };
module.exports = { loadConfig, getModulePositions, moduleHasValidPosition, getAvailableModulePositions, checkConfigFile, ConfigError };
+6 -6
View File
@@ -1,7 +1,7 @@
const fs = require("node:fs");
const Log = require("../../../js/logger");
const { checkConfigFile } = require("../../../js/utils");
const { checkConfigFile, ConfigError } = require("../../../js/utils");
const createConfigObject = (modules) => ({
configFilename: "config.js",
@@ -14,13 +14,13 @@ const runCheck = (modules) => {
};
const expectExitForModules = (modules) => {
vi.spyOn(process, "exit").mockImplementation((code) => {
throw new Error(`process.exit:${code}`);
vi.spyOn(process, "exit").mockImplementation(() => {
throw new ConfigError("");
});
expect(() => {
runCheck(modules);
}).toThrow("process.exit:1");
}).toThrow(ConfigError);
};
describe("utils", () => {
@@ -69,8 +69,8 @@ describe("utils", () => {
});
it("warns for unknown positions without exiting", () => {
const exitSpy = vi.spyOn(process, "exit").mockImplementation((code) => {
throw new Error(`process.exit:${code}`);
const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
throw new ConfigError("");
});
expect(() => {