mirror of
https://github.com/MichMich/MagicMirror.git
synced 2026-07-22 14:23:51 -07:00
systeminformation thread not ending: move error handling from utils to app (#4160)
This commit is contained in:
@@ -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
@@ -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 };
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user