aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/server/ActionUtilities.ts5
-rw-r--r--src/server/ChildProcessUtilities/daemon/session.ts126
2 files changed, 79 insertions, 52 deletions
diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts
index 2e62443c6..053576a92 100644
--- a/src/server/ActionUtilities.ts
+++ b/src/server/ActionUtilities.ts
@@ -6,7 +6,10 @@ import * as rimraf from "rimraf";
import { yellow, Color } from 'colors';
const projectRoot = path.resolve(__dirname, "../../");
-export function pathFromRoot(relative: string) {
+export function pathFromRoot(relative?: string) {
+ if (!relative) {
+ return projectRoot;
+ }
return path.resolve(projectRoot, relative);
}
diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts
index d6c8ddb64..833e90581 100644
--- a/src/server/ChildProcessUtilities/daemon/session.ts
+++ b/src/server/ChildProcessUtilities/daemon/session.ts
@@ -1,14 +1,14 @@
import * as request from "request-promise";
-import { log_execution } from "../../ActionUtilities";
+import { log_execution, pathFromRoot } from "../../ActionUtilities";
import { red, yellow, cyan, green, Color } from "colors";
import * as nodemailer from "nodemailer";
import { MailOptions } from "nodemailer/lib/json-transport";
-import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs";
+import { writeFileSync, existsSync, mkdirSync } from "fs";
import { resolve } from 'path';
import { ChildProcess, exec } from "child_process";
const killport = require("kill-port");
-const identifier = yellow("__daemon__:");
+const identifier = yellow("__session_manager__:");
process.on('SIGINT', () => current_backup?.kill("SIGTERM"));
@@ -32,16 +32,30 @@ function identifiedLog(message?: any, ...optionalParams: any[]) {
console.log(identifier, message, ...optionalParams);
}
+if (!["win32", "darwin"].includes(process.platform)) {
+ identifiedLog(red("Invalid operating system: this script is supported only on Mac and Windows."));
+ process.exit(1);
+}
+
+const onWindows = process.platform === "win32";
const LOCATION = "http://localhost";
+const heartbeat = `${LOCATION}:1050/serverHeartbeat`;
const recipient = "samuel_wilkins@brown.edu";
const frequency = 10;
const { pid } = process;
let restarting = false;
let count = 0;
-identifiedLog("Initializing daemon...");
+function startServerCommand() {
+ if (onWindows) {
+ return '"C:\\Program Files\\Git\\git-bash.exe" -c "npm run start-release"';
+ }
+ return `osascript -e 'tell app "Terminal"\ndo script "cd ${pathFromRoot()} && npm run start-release"\nend tell'`;
+}
+
+identifiedLog("Initializing session...");
-writeLocalPidLog("daemon", pid);
+writeLocalPidLog("session_manager", pid);
function writeLocalPidLog(filename: string, contents: any) {
const path = `./logs/current_${filename}_pid.log`;
@@ -53,56 +67,66 @@ function timestamp() {
return `@ ${new Date().toISOString()}`;
}
+async function clear_ports(...targets: number[]) {
+ return Promise.all(targets.map(port => {
+ const task = killport(port, 'tcp');
+ return task.catch((error: any) => identifiedLog(red(error)));
+ }));
+}
+
let current_backup: ChildProcess | undefined = undefined;
-async function listen() {
- identifiedLog(yellow(`Beginning to poll server heartbeat every ${frequency} seconds...\n`));
- if (!LOCATION) {
- identifiedLog(red("No location specified for persistence daemon. Please include as a command line environment variable or in a .env file."));
- process.exit(0);
- }
- const heartbeat = `${LOCATION}:1050/serverHeartbeat`;
- // if this is on our remote server, the server must be run in release mode
- // const suffix = LOCATION.includes("localhost") ? "" : "-release";
- setInterval(async () => {
- let error: any;
- try {
- await request.get(heartbeat);
- if (restarting) {
- addLogEntry("Backup server successfully " + count ? "restarted" : "started", green);
- count++;
- }
- restarting = false;
- } catch (e) {
- error = e;
- } finally {
- if (error) {
- if (!restarting) {
- restarting = true;
- if (count) {
- addLogEntry("Detected a server crash", red);
- current_backup?.kill("SIGTERM");
- identifiedLog(yellow("Cleaning up previous connections..."));
- await killport(1050, 'tcp').catch((error: any) => identifiedLog(red(error)));
- await killport(4321, 'tcp').catch((error: any) => identifiedLog(red(error)));
- identifiedLog(yellow("Connections cleared."));
- await log_execution({
- startMessage: identifier + " Sending crash notification email",
- endMessage: ({ error, result }) => {
- const success = error === null && result === true;
- return identifier + ` ${(success ? `Notification successfully sent to` : `Failed to notify`)} ${recipient} ${timestamp()}`;
- },
- action: async () => notify(error || "Hmm, no error to report..."),
- color: cyan
- });
- identifiedLog(green("Initiating server restart..."));
- }
- current_backup = exec('"C:\\Program Files\\Git\\git-bash.exe" -c "npm run start-release"', err => identifiedLog(err?.message || "Previous server process exited."));
- writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`);
+async function checkHeartbeat() {
+ let error: any;
+ try {
+ await request.get(heartbeat);
+ if (restarting) {
+ addLogEntry(`Backup server successfully ${count ? "restarted" : "started"}`, green);
+ count++;
+ }
+ restarting = false;
+ } catch (e) {
+ error = e;
+ } finally {
+ if (error) {
+ if (!restarting) {
+ restarting = true;
+ if (count) {
+ console.log();
+ addLogEntry("Detected a server crash", red);
+ current_backup?.kill("SIGTERM");
+
+ identifiedLog(yellow("Cleaning up previous connections..."));
+ await clear_ports(1050, 4321);
+ identifiedLog(yellow("Finished attempting to clear all ports. Any failures will be printed in red immediately above."));
+
+ await log_execution({
+ startMessage: identifier + " Sending crash notification email",
+ endMessage: ({ error, result }) => {
+ const success = error === null && result === true;
+ return identifier + ` ${(success ? `Notification successfully sent to` : `Failed to notify`)} ${recipient} ${timestamp()}`;
+ },
+ action: async () => notify(error || "Hmm, no error to report..."),
+ color: cyan
+ });
+
+ identifiedLog(green("Initiating server restart..."));
}
+ current_backup = exec(startServerCommand(), err => identifiedLog(err?.message || count ? "Previous server process exited." : "Spawned initial server."));
+ writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`);
}
}
- }, 1000 * 10);
+ }
+}
+
+async function startListening() {
+ identifiedLog(yellow(`After initialization, will poll server heartbeat every ${frequency} seconds...\n`));
+ if (!LOCATION) {
+ identifiedLog(red("No location specified for session manager. Please include as a command line environment variable or in a .env file."));
+ process.exit(0);
+ }
+ await checkHeartbeat();
+ setInterval(checkHeartbeat, 1000 * frequency);
}
function emailText(error: any) {
@@ -133,4 +157,4 @@ async function notify(error: any) {
});
}
-listen(); \ No newline at end of file
+startListening(); \ No newline at end of file