diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/server/index.ts | 41 | ||||
-rw-r--r-- | src/server/session_manager/crash_email.ts | 36 | ||||
-rw-r--r-- | src/server/session_manager/input_manager.ts | 16 | ||||
-rw-r--r-- | src/server/session_manager/session_manager_cluster.ts | 11 |
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); + } }); } |