Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split startup phases #348

Merged
merged 3 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 3 additions & 282 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
const path = require("path");
const http = require("http");
const fs = require("fs");
const readline = require("readline");
const process = require("process");
const mongodb = require("mongodb");
const pkg = require("./package.json");
const { exec } = require("child_process");
const uuid = require("uuid");
const { URL } = require("url");


const env = require("dotenv").config({
Expand Down Expand Up @@ -141,281 +137,9 @@ if (process.env.GC_INTERVAL !== null && global.gc) {
}


const init_db = () => {
return new Promise((resolve, reject) => {

logger.debug("Init Database...");

let url = new URL(`mongodb://${process.env.DATABASE_HOST}:${process.env.DATABASE_PORT}/${process.env.DATABASE_NAME}`);

if (process.env.DATABASE_AUTH_USERNAME) {
url.username = process.env.DATABASE_AUTH_USERNAME;
}

if (process.env.DATABASE_AUTH_USERNAME) {
url.password = process.env.DATABASE_AUTH_PASSWORD;
}

if (process.env.DATABASE_URL) {
console.log("OVerride DATBAASE_URL");
Object.assign(url, new URL(process.env.DATABASE_URL));
}

// feedback
logger.verbose(`Connecting to "%s"...`, url.toString());


mongodb.MongoClient.connect(url.toString(), {
useUnifiedTopology: true,
useNewUrlParser: true,
//connectTimeoutMS: Number(process.env.DATABASE_TIMEOUT) * 1000, // #9
//socketTimeoutMS: Number(process.env.DATABASE_TIMEOUT) * 1000 // #9
}, async (err, client) => {

if (err) {
logger.error(err, "Could not connect to database");
return reject(err);
}

// monky patch db instance
// use this instance in other files
//mongodb.client = client.db(process.env.DATABASE_NAME);
mongodb.connection = client;
mongodb.client = client.db();


client.on("error", (err) => {
logger.error(err, "Could not connecto to databse: %s", err.message);
});

client.on("close", () => {
process.exit(1000);
});


try {

// test authenticiation
// throws a error is auth is noc successfull
await mongodb.client.stats();

// feedback
logger.info(`Connected to "mongodb://${url.hostname}:${url.port}${url.pathname}"`);

process.once("SIGINT", () => {
mongodb.connection.close(() => {
logger.info(`Connection closed from "mongodb://${url.hostname}:${url.port}${url.pathname}"`);
});
});

resolve();

} catch (err) {

if (err?.code == 13) {
logger.error("Invalid database credentials!");
}

client.emit("error", err);
reject(err);

}


});

});
};


const init_components = () => {
return new Promise((resolve) => {

logger.debug("Init components...");

const componentNames = [
"devices",
"endpoints",
"plugins",
"rooms",
"ssdp",
"store",
"users",
"vault",
"webhooks",
"mqtt",
"mdns",
"scenes"
].sort(() => {

// pseudo randomize start/init of components
// https://stackoverflow.com/a/18650169/5781499
return 0.5 - Math.random();

});

let componentConter = 0;
//let counter = componentNames.length;


// map over array
// create from each promise
// use Promise.all() ?
// better/quicker start?
componentNames.forEach((name) => {
try {

// this should be trace method
logger.verbose(`Starting component "${name}"`);

let component = require(`./components/${name}/index.js`);

component.events.on("ready", () => {

componentConter += 1;

logger.debug(`Component "${name}" ready to use. (${componentConter}/${componentNames.length})`);

if (componentConter === componentNames.length) {
logger.info(`All ${componentNames.length} Components ready`);
resolve();
}

});

// see issue #53, this should fire:
// the procces should not exit with a "unhandled execption"
// the try/catch block is for unhandled exception, not for startup errors
component.events.on("error", (err) => {
logger.error(err, `Component "${name}" error!`);
process.exit(1); // fix #53
});

} catch (err) {

console.error(err, "Component error");
process.exit(800);

}
});

});
};


const init_http = () => {
return new Promise((resolve, reject) => {

logger.debug("Init http server...");

const servers = [

// http server for ip/port
new Promise((resolve, reject) => {
if (process.env.HTTP_ADDRESS !== "") {

let server = http.createServer();

server.on("error", (err) => {
logger.error(err, `Could not start http server: ${err.message}`);
reject(err);
});

server.on("listening", () => {

let addr = server.address();
logger.info(`HTTP Server listening on http://${addr.address}:${addr.port}`);

resolve(server);

});

server.on("close", () => {
logger.info(`HTTP Server closed on http://${process.env.HTTP_ADDRESS}:${process.env.HTTP_PORT}`);
});

require("./routes")(server);

// bind/start http server
server.listen(Number(process.env.HTTP_PORT), process.env.HTTP_ADDRESS);

} else {
resolve();
}
}),

// http server fo unix socket
new Promise((resolve, reject) => {
if (process.env.HTTP_SOCKET !== "") {

let server = http.createServer();

server.on("error", (err) => {

logger.error(err, `Could not start http server: ${err.message}`);
reject(err);

});

server.on("listening", () => {

logger.info(`HTTP Server listening on ${process.env.HTTP_SOCKET}`);

resolve(server);

});

server.on("close", () => {
logger.info(`HTTP Server closed on ${process.env.HTTP_SOCKET}`);
});

require("./routes")(server);

try {

// cleanup
fs.unlinkSync(process.env.HTTP_SOCKET);

} catch (err) {
if (err.code !== "ENOENT") {

reject(err);

}
} finally {

// bind/start http server
server.listen(process.env.HTTP_SOCKET);

}

} else {
resolve();
}
})

];

Promise.all(servers).then((servers) => {

process.once("SIGINT", () => {
servers.forEach((server) => {
server.close();
});
});

resolve();

}).catch((err) => {

logger.error(err, "Could not start http server(s)", err);

reject(err);

});

});
};
const init_db = require("./system/init/init.database.js")(logger);
const init_components = require("./system/init/init.components.js")(logger);
const init_http = require("./system/init/init.http-server.js")(logger);


// NOTE: Could/should be removed
Expand Down Expand Up @@ -544,9 +268,6 @@ const starter = new Promise((resolve) => {

process.once("SIGINT", () => {
logger.warn("Shuting down...");
setTimeout(() => {
process.exit();
}, 1000);
});

});
2 changes: 1 addition & 1 deletion system/component/class.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ module.exports = class COMPONENT extends COMMON {
constructor(name, schema, parent) {

if (parent) {
require("../prevent_cross_load")(parent);
//require("../prevent_cross_load")(parent);
}

super(require("../../system/logger").create(name));
Expand Down
77 changes: 77 additions & 0 deletions system/init/init.components.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const path = require("path");

module.exports = (logger) => {
return () => {
return new Promise((resolve) => {

logger.debug("Init components...");

const componentNames = [
"devices",
"endpoints",
"plugins",
"rooms",
"ssdp",
"store",
"users",
"vault",
"webhooks",
"mqtt",
"mdns",
"scenes"
].sort(() => {

// pseudo randomize start/init of components
// https://stackoverflow.com/a/18650169/5781499
return 0.5 - Math.random();

});

let componentConter = 0;
//let counter = componentNames.length;


// map over array
// create from each promise
// use Promise.all() ?
// better/quicker start?
componentNames.forEach((name) => {
try {

// this should be trace method
logger.verbose(`Starting component "${name}"`);

let component = require(path.resolve(process.cwd(), `components/${name}/index.js`));

component.events.on("ready", () => {

componentConter += 1;

logger.debug(`Component "${name}" ready to use. (${componentConter}/${componentNames.length})`);

if (componentConter === componentNames.length) {
logger.info(`All ${componentNames.length} Components ready`);
resolve();
}

});

// see issue #53, this should fire:
// the procces should not exit with a "unhandled execption"
// the try/catch block is for unhandled exception, not for startup errors
component.events.on("error", (err) => {
logger.error(err, `Component "${name}" error!`);
process.exit(1); // fix #53
});

} catch (err) {

console.error(err, "Component error");
process.exit(800);

}
});

});
};
};
Loading
Loading