aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/server/index.ts41
-rw-r--r--src/server/session_manager/crash_email.ts36
-rw-r--r--src/server/session_manager/input_manager.ts16
-rw-r--r--src/server/session_manager/session_manager_cluster.ts11
4 files changed, 92 insertions, 12 deletions
diff --git a/src/server/index.ts b/src/server/index.ts
index 2cc35ccec..83413c23c 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -21,10 +21,17 @@ import UploadManager from "./ApiManagers/UploadManager";
import { log_execution } from "./ActionUtilities";
import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager";
import GooglePhotosManager from "./ApiManagers/GooglePhotosManager";
-import { yellow, red } from "colors";
+import { yellow, red, cyan } from "colors";
import { disconnect } from "../server/Initialization";
-import { ProcessFactory, Logger } from "./ProcessFactory";
+import { Logger } from "./ProcessFactory";
+import { isMaster, on, fork, workers } 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";
+const killport = require("kill-port");
+export const onWindows = process.platform === "win32";
export const publicDirectory = path.resolve(__dirname, "public");
export const filesDirectory = path.resolve(publicDirectory, "files");
@@ -128,11 +135,37 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
WebSocket.initialize(serverPort, isRelease);
}
-(async function start() {
+async function start() {
await log_execution({
startMessage: "\nstarting execution of preliminary functions",
endMessage: "completed preliminary functions\n",
action: preliminaryFunctions
});
await initializeServer({ serverPort: 1050, routeSetter });
-})();
+}
+
+const admin = ["samuel_wilkins@brown.edu"];
+if (isMaster) {
+ fork();
+ on("exit", ({ process: { pid } }, code, signal) => {
+ const prompt = `Server worker with process id ${pid} has died with code ${code}${signal === null ? "" : `, having encountered signal ${signal}`}.\n`;
+ console.log(cyan(prompt));
+ fork();
+ });
+ const { registerCommand } = new InputManager({ identifier });
+ registerCommand("exit", [], () => execSync(onWindows ? "taskkill /f /im node.exe" : "killall -9 node"));
+ registerCommand("restart", [], () => {
+ for (const id in workers) {
+ workers[id]?.kill();
+ }
+ fork();
+ });
+} else {
+ process.on('uncaughtException', async error => {
+ await CrashEmail.dispatch(error, admin);
+ console.error(red(`Crash event detected @ ${new Date().toUTCString()}`));
+ console.error(error.message);
+ process.exit(1);
+ });
+ start();
+} \ No newline at end of file
diff --git a/src/server/session_manager/crash_email.ts b/src/server/session_manager/crash_email.ts
new file mode 100644
index 000000000..7783cd779
--- /dev/null
+++ b/src/server/session_manager/crash_email.ts
@@ -0,0 +1,36 @@
+import * as nodemailer from "nodemailer";
+import { MailOptions } from "nodemailer/lib/json-transport";
+
+export namespace CrashEmail {
+
+ export async function dispatch(error: Error, recipients: string[]): Promise<boolean[]> {
+ const smtpTransport = nodemailer.createTransport({
+ service: 'Gmail',
+ auth: {
+ user: 'brownptcdash@gmail.com',
+ pass: 'browngfx1'
+ }
+ });
+ return Promise.all(recipients.map(recipient => new Promise<boolean>(resolve => {
+ const mailOptions = {
+ to: recipient,
+ from: 'brownptcdash@gmail.com',
+ subject: 'Dash Server Crash',
+ text: emailText(recipient, error)
+ } as MailOptions;
+ smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null));
+ })));
+ }
+
+ function emailText(recipient: string, { name, message, stack }: Error) {
+ return [
+ `Hey ${recipient.split("@")[0]},`,
+ "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:",
+ `name:\n${name}`,
+ `message:\n${message}`,
+ `stack:\n${stack}`,
+ "The server is already restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress."
+ ].join("\n\n");
+ }
+
+} \ No newline at end of file
diff --git a/src/server/session_manager/input_manager.ts b/src/server/session_manager/input_manager.ts
index a95e6baae..133b7144a 100644
--- a/src/server/session_manager/input_manager.ts
+++ b/src/server/session_manager/input_manager.ts
@@ -7,9 +7,10 @@ export interface Configuration {
isCaseSensitive?: boolean;
}
+type Action = (parsedArgs: IterableIterator<string>) => any | Promise<any>;
export interface Registration {
- argPattern: RegExp[];
- action: (parsedArgs: IterableIterator<string>) => any | Promise<any>;
+ argPatterns: RegExp[];
+ action: Action;
}
export default class InputManager {
@@ -42,9 +43,10 @@ export default class InputManager {
return `${this.identifier} commands: { ${members.sort().join(", ")} }`;
}
- public registerCommand = (basename: string, argPattern: RegExp[], action: any | Promise<any>) => {
+ public registerCommand = (basename: string, argPatterns: (RegExp | string)[], action: Action) => {
const existing = this.commandMap.get(basename);
- const registration = { argPattern, action };
+ const converted = argPatterns.map(input => input instanceof RegExp ? input : new RegExp(input));
+ const registration = { argPatterns: converted, action };
if (existing) {
existing.push(registration);
} else {
@@ -74,14 +76,14 @@ export default class InputManager {
const registered = this.commandMap.get(command);
if (registered) {
const { length } = args;
- const candidates = registered.filter(({ argPattern: { length: count } }) => count === length);
- for (const { argPattern, action } of candidates) {
+ const candidates = registered.filter(({ argPatterns: { length: count } }) => count === length);
+ for (const { argPatterns, action } of candidates) {
const parsed: string[] = [];
let matched = false;
if (length) {
for (let i = 0; i < length; i++) {
let matches: RegExpExecArray | null;
- if ((matches = argPattern[i].exec(args[i])) === null) {
+ if ((matches = argPatterns[i].exec(args[i])) === null) {
break;
}
parsed.push(matches[0]);
diff --git a/src/server/session_manager/session_manager_cluster.ts b/src/server/session_manager/session_manager_cluster.ts
index bfe6187c3..546465c03 100644
--- a/src/server/session_manager/session_manager_cluster.ts
+++ b/src/server/session_manager/session_manager_cluster.ts
@@ -4,19 +4,28 @@ import { createServer } from "http";
const capacity = cpus().length;
+let thrown = false;
+
if (isMaster) {
console.log(capacity);
for (let i = 0; i < capacity; i++) {
fork();
}
on("exit", (worker, code, signal) => {
- console.log(`worker ${worker.process.pid} died`);
+ console.log(`worker ${worker.process.pid} died with code ${code} and signal ${signal}`);
+ fork();
});
} else {
const port = 1234;
createServer().listen(port, () => {
console.log('process id local', process.pid);
console.log(`http server started at port ${port}`);
+ if (!thrown) {
+ thrown = true;
+ setTimeout(() => {
+ throw new Error("Hey I'm a fake error!");
+ }, 1000);
+ }
});
}