Skip to content

Commit 7abbe62

Browse files
authored
systeminformation thread not ending: move error handling from utils to app (#4160)
1 parent 3c514df commit 7abbe62

3 files changed

Lines changed: 91 additions & 86 deletions

File tree

js/app.js

Lines changed: 71 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const { setGlobalDispatcher, Agent } = require("undici");
1414

1515
const Server = require("./server");
1616
const Utils = require("./utils");
17+
const { ConfigError } = require("./utils");
1718

1819
const { getEnvVarsAsObj } = require("#server_functions");
1920
// common timeout value, provide environment override in case
@@ -183,80 +184,92 @@ function App () {
183184
* @returns {Promise<object>} the config used
184185
*/
185186
this.start = async function () {
186-
const configObj = Utils.loadConfig();
187-
global.config = configObj.fullConf;
188-
const config = global.config;
189-
Utils.checkConfigFile(configObj);
190-
191-
global.defaultModulesDir = config.defaultModulesDir;
192-
defaultModules = require(`${global.root_path}/${global.defaultModulesDir}/defaultmodules`);
193-
194-
Log.setLogLevel(config.logLevel);
195-
196-
env = getEnvVarsAsObj();
197-
// check for deprecated css/custom.css and move it to new location
198-
if ((!fs.existsSync(`${global.root_path}/${env.customCss}`)) && (fs.existsSync(`${global.root_path}/css/custom.css`))) {
199-
try {
200-
fs.renameSync(`${global.root_path}/css/custom.css`, `${global.root_path}/${env.customCss}`);
201-
Log.warn(`WARNING! Your custom css file was moved from ${global.root_path}/css/custom.css to ${global.root_path}/${env.customCss}`);
202-
} catch {
203-
Log.warn("WARNING! Your custom css file is currently located in the css folder. Please move it to the config folder!");
187+
try {
188+
const configObj = Utils.loadConfig();
189+
global.config = configObj.fullConf;
190+
const config = global.config;
191+
Utils.checkConfigFile(configObj);
192+
193+
global.defaultModulesDir = config.defaultModulesDir;
194+
defaultModules = require(`${global.root_path}/${global.defaultModulesDir}/defaultmodules`);
195+
196+
Log.setLogLevel(config.logLevel);
197+
198+
env = getEnvVarsAsObj();
199+
// check for deprecated css/custom.css and move it to new location
200+
if ((!fs.existsSync(`${global.root_path}/${env.customCss}`)) && (fs.existsSync(`${global.root_path}/css/custom.css`))) {
201+
try {
202+
fs.renameSync(`${global.root_path}/css/custom.css`, `${global.root_path}/${env.customCss}`);
203+
Log.warn(`WARNING! Your custom css file was moved from ${global.root_path}/css/custom.css to ${global.root_path}/${env.customCss}`);
204+
} catch {
205+
Log.warn("WARNING! Your custom css file is currently located in the css folder. Please move it to the config folder!");
206+
}
204207
}
205-
}
206208

207-
// get the used module positions
208-
Utils.getModulePositions();
209-
210-
let modules = [];
211-
for (const module of config.modules) {
212-
if (module.disabled) continue;
213-
if (module.module) {
214-
if (Utils.moduleHasValidPosition(module.position) || typeof (module.position) === "undefined") {
215-
// Only add this module to be loaded if it is not a duplicate (repeated instance of the same module)
216-
if (!modules.includes(module.module)) {
217-
modules.push(module.module);
209+
// get the used module positions
210+
Utils.getModulePositions();
211+
212+
let modules = [];
213+
for (const module of config.modules) {
214+
if (module.disabled) continue;
215+
if (module.module) {
216+
if (Utils.moduleHasValidPosition(module.position) || typeof (module.position) === "undefined") {
217+
// Only add this module to be loaded if it is not a duplicate (repeated instance of the same module)
218+
if (!modules.includes(module.module)) {
219+
modules.push(module.module);
220+
}
221+
} else {
222+
Log.warn("Invalid module position found for this configuration:" + `\n${JSON.stringify(module, null, 2)}`);
218223
}
219224
} else {
220-
Log.warn("Invalid module position found for this configuration:" + `\n${JSON.stringify(module, null, 2)}`);
225+
Log.warn("No module name found for this configuration:" + `\n${JSON.stringify(module, null, 2)}`);
221226
}
222-
} else {
223-
Log.warn("No module name found for this configuration:" + `\n${JSON.stringify(module, null, 2)}`);
224227
}
225-
}
226228

227-
setGlobalDispatcher(new Agent({ connect: { timeout: fetch_timeout } }));
229+
setGlobalDispatcher(new Agent({ connect: { timeout: fetch_timeout } }));
228230

229-
await loadModules(modules);
231+
await loadModules(modules);
230232

231-
httpServer = new Server(configObj);
232-
const { app, io } = await httpServer.open();
233-
Log.log("Server started ...");
233+
httpServer = new Server(configObj);
234+
const { app, io } = await httpServer.open();
235+
Log.log("Server started ...");
234236

235-
const nodePromises = [];
236-
for (let nodeHelper of nodeHelpers) {
237-
nodeHelper.setExpressApp(app);
238-
nodeHelper.setSocketIO(io);
237+
const nodePromises = [];
238+
for (let nodeHelper of nodeHelpers) {
239+
nodeHelper.setExpressApp(app);
240+
nodeHelper.setSocketIO(io);
239241

240-
try {
241-
nodePromises.push(nodeHelper.start());
242-
} catch (error) {
243-
Log.error(`Error when starting node_helper for module ${nodeHelper.name}:`);
244-
Log.error(error);
242+
try {
243+
nodePromises.push(nodeHelper.start());
244+
} catch (error) {
245+
Log.error(`Error when starting node_helper for module ${nodeHelper.name}:`);
246+
Log.error(error);
247+
}
245248
}
246-
}
247249

248-
const results = await Promise.allSettled(nodePromises);
250+
const results = await Promise.allSettled(nodePromises);
249251

250-
// Log errors that happened during async node_helper startup
251-
results.forEach((result) => {
252-
if (result.status === "rejected") {
253-
Log.error(result.reason);
254-
}
255-
});
252+
// Log errors that happened during async node_helper startup
253+
results.forEach((result) => {
254+
if (result.status === "rejected") {
255+
Log.error(result.reason);
256+
}
257+
});
256258

257-
Log.log("Sockets connected & modules started ...");
259+
Log.log("Sockets connected & modules started ...");
260+
261+
return global.config;
262+
} catch (err) {
263+
// planned ConfigErrors already logged their message before throwing
264+
if (!(err instanceof ConfigError)) {
265+
Log.error("Unexpected error during startup:", err);
266+
}
258267

259-
return global.config;
268+
const int32 = new Int32Array(new SharedArrayBuffer(4));
269+
// wait 1000ms before exiting so that child processes (e.g. systeminformation) have some additional time
270+
Atomics.wait(int32, 0, 0, 1000);
271+
process.exit(1);
272+
}
260273
};
261274

262275
/**
@@ -328,20 +341,6 @@ function App () {
328341
await this.stop();
329342
process.exit(0);
330343
});
331-
332-
/**
333-
*
334-
* @param {number} ms milliseconds to wait
335-
*/
336-
function blockingSleep (ms) {
337-
const int32 = new Int32Array(new SharedArrayBuffer(4));
338-
Atomics.wait(int32, 0, 0, ms);
339-
}
340-
341-
process.on("exit", () => {
342-
// wait before exiting so that child processes (e.g. systeminformation) have some additional time
343-
blockingSleep(1000);
344-
});
345344
}
346345

347346
module.exports = new App();

js/utils.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ const { getConfigFilePath } = require("#server_functions");
1414

1515
const linter = new Linter({ configType: "flat" });
1616

17+
class ConfigError extends Error {
18+
constructor (message) {
19+
super(message);
20+
this.name = "ConfigError";
21+
}
22+
}
23+
1724
const requireFromString = (src) => {
1825
const m = new module.constructor();
1926
m._compile(src, "");
@@ -172,9 +179,8 @@ const loadConfig = () => {
172179
} else {
173180
Log.error(`Cannot access config file: ${configFilename}\n${error.message}`);
174181
}
175-
process.exit(1);
182+
throw new ConfigError("");
176183
}
177-
return {};
178184
};
179185

180186
/**
@@ -220,7 +226,7 @@ const checkConfigFile = (configObject) => {
220226
errorMessage += `\nLine ${error.line} column ${error.column}: ${error.message}`;
221227
}
222228
Log.error(errorMessage);
223-
process.exit(1);
229+
throw new ConfigError("");
224230
}
225231
};
226232

@@ -242,27 +248,27 @@ const validateModulePositions = (data) => {
242248
// `modules` always exists (defaults.js provides a default array), but guard against it being overridden with a non-array value
243249
if (data.modules !== undefined && !Array.isArray(data.modules)) {
244250
Log.error("This module configuration contains errors:\nmodules must be an array");
245-
process.exit(1);
251+
throw new ConfigError("");
246252
}
247253

248254
// Validate each module entry
249255
for (const [index, mod] of (data.modules ?? []).entries()) {
250256
// Each module entry must be an object so we can safely inspect its fields
251257
if (mod === null || typeof mod !== "object" || Array.isArray(mod)) {
252258
Log.error(`This module configuration contains errors:\n${JSON.stringify(mod, null, 2)}\nmodule entry must be an object`);
253-
process.exit(1);
259+
throw new ConfigError("");
254260
}
255261

256262
// `module` (the module name) is required and must be a string
257263
if (typeof mod.module !== "string") {
258264
Log.error(`This module configuration contains errors:\n${JSON.stringify(mod, null, 2)}\nmodule: must be a string`);
259-
process.exit(1);
265+
throw new ConfigError("");
260266
}
261267

262268
// `position` is optional, but must be a string when provided
263269
if (mod.position !== undefined && typeof mod.position !== "string") {
264270
Log.error(`This module configuration contains errors:\n${JSON.stringify(mod, null, 2)}\nposition: must be a string`);
265-
process.exit(1);
271+
throw new ConfigError("");
266272
}
267273

268274
// `position` is optional, but when set it must match a known region
@@ -275,4 +281,4 @@ const validateModulePositions = (data) => {
275281
Log.info(styleText("green", "Your modules structure configuration doesn't contain errors :)"));
276282
};
277283

278-
module.exports = { loadConfig, getModulePositions, moduleHasValidPosition, getAvailableModulePositions, checkConfigFile };
284+
module.exports = { loadConfig, getModulePositions, moduleHasValidPosition, getAvailableModulePositions, checkConfigFile, ConfigError };

tests/unit/classes/utils_spec.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const fs = require("node:fs");
22

33
const Log = require("../../../js/logger");
4-
const { checkConfigFile } = require("../../../js/utils");
4+
const { checkConfigFile, ConfigError } = require("../../../js/utils");
55

66
const createConfigObject = (modules) => ({
77
configFilename: "config.js",
@@ -14,13 +14,13 @@ const runCheck = (modules) => {
1414
};
1515

1616
const expectExitForModules = (modules) => {
17-
vi.spyOn(process, "exit").mockImplementation((code) => {
18-
throw new Error(`process.exit:${code}`);
17+
vi.spyOn(process, "exit").mockImplementation(() => {
18+
throw new ConfigError("");
1919
});
2020

2121
expect(() => {
2222
runCheck(modules);
23-
}).toThrow("process.exit:1");
23+
}).toThrow(ConfigError);
2424
};
2525

2626
describe("utils", () => {
@@ -69,8 +69,8 @@ describe("utils", () => {
6969
});
7070

7171
it("warns for unknown positions without exiting", () => {
72-
const exitSpy = vi.spyOn(process, "exit").mockImplementation((code) => {
73-
throw new Error(`process.exit:${code}`);
72+
const exitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
73+
throw new ConfigError("");
7474
});
7575

7676
expect(() => {

0 commit comments

Comments
 (0)