aboutsummaryrefslogtreecommitdiff
path: root/src/server/session.ts
diff options
context:
space:
mode:
authorSam Wilkins <samwilkins333@gmail.com>2020-01-02 17:03:41 -0800
committerSam Wilkins <samwilkins333@gmail.com>2020-01-02 17:03:41 -0800
commit0b21ee48c04c6690e574f8ecfb24c7447136bff0 (patch)
treeafb880c0f899f1cae67523d8fdbfc14134763ed8 /src/server/session.ts
parent7d9dc9e647542b0a2fdb9a98cb02e3c9ffc5ff12 (diff)
stable, clustered session manager
Diffstat (limited to 'src/server/session.ts')
-rw-r--r--src/server/session.ts101
1 files changed, 101 insertions, 0 deletions
diff --git a/src/server/session.ts b/src/server/session.ts
new file mode 100644
index 000000000..53e1be1b1
--- /dev/null
+++ b/src/server/session.ts
@@ -0,0 +1,101 @@
+import { yellow, red, cyan, magenta, green } from "colors";
+import { isMaster, on, fork, setupMaster, Worker } from "cluster";
+import { identifier } from "./session_manager/config";
+import InputManager from "./session_manager/input_manager";
+import { execSync } from "child_process";
+import { CrashEmail } from "./session_manager/crash_email";
+import { get } from "request-promise";
+import { WebSocket } from "./Websocket/Websocket";
+import { Utils } from "../Utils";
+import { MessageStore } from "./Message";
+
+const onWindows = process.platform === "win32";
+const heartbeat = `http://localhost:1050/serverHeartbeat`;
+const admin = ["samuel_wilkins@brown.edu"];
+
+export namespace Session {
+
+ const masterIdentifier = yellow("__master__");
+ const workerIdentifier = magenta("__worker__");
+
+ export async function initialize(work: Function) {
+ let listening = false;
+ let active: Worker;
+ if (isMaster) {
+ process.on("uncaughtException", error => {
+ if (error.message !== "Channel closed") {
+ console.log(`${masterIdentifier}: ${red(error.message)}`);
+ if (error.stack) {
+ console.log(`${masterIdentifier}:\n${red(error.stack)}`);
+ }
+ }
+ });
+ setupMaster({ silent: true });
+ const spawn = () => {
+ if (active && !active.isDead()) {
+ active.process.kill();
+ }
+ active = fork();
+ active.on("message", ({ update }) => {
+ if (update) {
+ console.log(`${workerIdentifier}: ${update}`);
+ }
+ });
+ };
+ spawn();
+ on("exit", ({ process: { pid } }, code, signal) => {
+ const prompt = `Server worker with process id ${pid} has exited with code ${code}${signal === null ? "" : `, having encountered signal ${signal}`}.`;
+ console.log(`${masterIdentifier}: ${cyan(prompt)}`);
+ spawn();
+ });
+ const restart = () => {
+ listening = false;
+ const prompt = `Server worker with process id ${active.process.pid} has been manually killed.`;
+ console.log(`${masterIdentifier}: ${cyan(prompt)}`);
+ spawn();
+ };
+ const { registerCommand } = new InputManager({ identifier });
+ registerCommand("exit", [], () => execSync(onWindows ? "taskkill /f /im node.exe" : "killall -9 node"));
+ registerCommand("restart", [], restart);
+ } else {
+ const notifyMaster = (update: string) => process.send?.({ update });
+ notifyMaster(green("initializing..."));
+ const gracefulExit = async (error: Error) => {
+ if (!listening) {
+ return;
+ }
+ listening = false;
+ await CrashEmail.dispatch(error, admin);
+ const { _socket } = WebSocket;
+ if (_socket) {
+ Utils.Emit(_socket, MessageStore.ConnectionTerminated, "Manual");
+ }
+ notifyMaster(red(`Crash event detected @ ${new Date().toUTCString()}`));
+ notifyMaster(red(error.message));
+ process.exit(1);
+ };
+ process.on('uncaughtException', gracefulExit);
+ const checkHeartbeat = async () => {
+ await new Promise<void>(resolve => {
+ setTimeout(async () => {
+ try {
+ await get(heartbeat);
+ if (!listening) {
+ notifyMaster(green("server is now successfully listening..."));
+ }
+ listening = true;
+ } catch (error) {
+ await gracefulExit(error);
+ } finally {
+ resolve();
+ }
+ }, 1000 * 15);
+ });
+ checkHeartbeat();
+ };
+ work();
+ checkHeartbeat();
+ }
+ }
+
+} \ No newline at end of file