aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Wilkins <samwilkins333@gmail.com>2020-01-08 03:42:14 -0500
committerSam Wilkins <samwilkins333@gmail.com>2020-01-08 03:42:14 -0500
commit7378b5d063d9da34d485c8384efa71ba83272a61 (patch)
tree6f97360904f21951171983c6a0aa797b02b551ce
parentda50d941f597fb3ec52bc3653d94f8c30affe552 (diff)
session cleanup
-rw-r--r--src/client/DocServer.ts4
-rw-r--r--src/server/DashSession.ts89
-rw-r--r--src/server/Session/session.ts69
-rw-r--r--src/server/index.ts2
4 files changed, 92 insertions, 72 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 47c63bfb7..ed7fbd7ba 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -82,7 +82,9 @@ export namespace DocServer {
Utils.AddServerHandler(_socket, MessageStore.UpdateField, respondToUpdate);
Utils.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
- Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, () => alert("Your connection to the server has been terminated."));
+ Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, () => {
+ alert("Your connection to the server has been terminated.");
+ });
}
function errorFunc(): never {
diff --git a/src/server/DashSession.ts b/src/server/DashSession.ts
index 9c36fa17f..c0ebc9687 100644
--- a/src/server/DashSession.ts
+++ b/src/server/DashSession.ts
@@ -3,59 +3,58 @@ import { Email } from "./ActionUtilities";
import { red, yellow } from "colors";
import { SolrManager } from "./ApiManagers/SearchManager";
import { execSync } from "child_process";
-import { isMaster } from "cluster";
import { Utils } from "../Utils";
import { WebSocket } from "./Websocket/Websocket";
import { MessageStore } from "./Message";
import { launchServer } from ".";
-const notificationRecipients = ["samuel_wilkins@brown.edu"];
-const signature = "-Dash Server Session Manager";
+/**
+* If we're the monitor (master) thread, we should launch the monitor logic for the session.
+* Otherwise, we must be on a worker thread that was spawned *by* the monitor (master) thread, and thus
+* our job should be to run the server.
+*/
+export class DashSessionAgent extends Session.AppliedSessionAgent {
-const monitorHooks: Session.MonitorNotifierHooks = {
- key: async (key, masterLog) => {
- const content = `The key for this session (started @ ${new Date().toUTCString()}) is ${key}.\n\n${signature}`;
- const failures = await Email.dispatchAll(notificationRecipients, "Server Termination Key", content);
- if (failures) {
- failures.map(({ recipient, error: { message } }) => masterLog(red(`dispatch failure @ ${recipient} (${yellow(message)})`)));
- return false;
- }
- return true;
- },
- crash: async ({ name, message, stack }, masterLog) => {
- const body = [
- "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");
- const content = `${body}\n\n${signature}`;
- const failures = await Email.dispatchAll(notificationRecipients, "Dash Web Server Crash", content);
- if (failures) {
- failures.map(({ recipient, error: { message } }) => masterLog(red(`dispatch failure @ ${recipient} (${yellow(message)})`)));
- return false;
- }
- return true;
- }
-};
+ private readonly notificationRecipients = ["samuel_wilkins@brown.edu"];
+ private readonly signature = "-Dash Server Session Manager";
-export class DashSessionAgent extends Session.AppliedSessionAgent {
+ protected async launchMonitor() {
+ const monitor = await Session.initializeMonitorThread({
+ key: async (key, masterLog) => {
+ const content = `The key for this session (started @ ${new Date().toUTCString()}) is ${key}.\n\n${this.signature}`;
+ const failures = await Email.dispatchAll(this.notificationRecipients, "Server Termination Key", content);
+ if (failures) {
+ failures.map(({ recipient, error: { message } }) => masterLog(red(`dispatch failure @ ${recipient} (${yellow(message)})`)));
+ return false;
+ }
+ return true;
+ },
+ crash: async ({ name, message, stack }, masterLog) => {
+ const body = [
+ "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");
+ const content = `${body}\n\n${this.signature}`;
+ const failures = await Email.dispatchAll(this.notificationRecipients, "Dash Web Server Crash", content);
+ if (failures) {
+ failures.map(({ recipient, error: { message } }) => masterLog(red(`dispatch failure @ ${recipient} (${yellow(message)})`)));
+ return false;
+ }
+ return true;
+ }
+ });
+ monitor.addReplCommand("pull", [], () => execSync("git pull", { stdio: ["ignore", "inherit", "inherit"] }));
+ monitor.addReplCommand("solr", [/start|stop/g], args => SolrManager.SetRunning(args[0] === "start"));
+ return monitor;
+ }
- /**
- * If we're the monitor (master) thread, we should launch the monitor logic for the session.
- * Otherwise, we must be on a worker thread that was spawned *by* the monitor (master) thread, and thus
- * our job should be to run the server.
- */
- protected async launchImplementation() {
- if (isMaster) {
- this.sessionMonitor = await Session.initializeMonitorThread(monitorHooks);
- this.sessionMonitor.addReplCommand("pull", [], () => execSync("git pull", { stdio: ["ignore", "inherit", "inherit"] }));
- this.sessionMonitor.addReplCommand("solr", [/start|stop/g], args => SolrManager.SetRunning(args[0] === "start"));
- } else {
- this.serverWorker = await Session.initializeWorkerThread(launchServer); // server initialization delegated to worker
- this.serverWorker.addExitHandler(() => Utils.Emit(WebSocket._socket, MessageStore.ConnectionTerminated, "Manual"));
- }
+ protected async launchServerWorker() {
+ const worker = await Session.initializeWorkerThread(launchServer); // server initialization delegated to worker
+ worker.addExitHandler(() => Utils.Emit(WebSocket._socket, MessageStore.ConnectionTerminated, "Manual"));
+ return worker;
}
} \ No newline at end of file
diff --git a/src/server/Session/session.ts b/src/server/Session/session.ts
index b22b6404d..cc9e7dd1a 100644
--- a/src/server/Session/session.ts
+++ b/src/server/Session/session.ts
@@ -22,46 +22,44 @@ export namespace Session {
export abstract class AppliedSessionAgent {
+ public killSession(graceful = true) {
+ const target = isMaster ? this.sessionMonitor : this.serverWorker;
+ target.killSession(graceful);
+ }
+
private launched = false;
- protected sessionMonitorRef: Session.Monitor | undefined;
+ private sessionMonitorRef: Session.Monitor | undefined;
public get sessionMonitor(): Session.Monitor {
if (!isMaster) {
throw new Error("Cannot access the session monitor directly from the server worker thread");
}
return this.sessionMonitorRef!;
}
- public set sessionMonitor(monitor: Session.Monitor) {
- if (!isMaster) {
- throw new Error("Cannot set the session monitor directly from the server worker thread");
- }
- this.sessionMonitorRef = monitor;
- }
- protected serverWorkerRef: Session.ServerWorker | undefined;
+ private serverWorkerRef: Session.ServerWorker | undefined;
public get serverWorker(): Session.ServerWorker {
if (isMaster) {
throw new Error("Cannot access the server worker directly from the session monitor thread");
}
return this.serverWorkerRef!;
}
- public set serverWorker(worker: Session.ServerWorker) {
- if (isMaster) {
- throw new Error("Cannot set the server worker directly from the session monitor thread");
- }
- this.serverWorkerRef = worker;
- }
public async launch(): Promise<void> {
if (!this.launched) {
this.launched = true;
- await this.launchImplementation();
+ if (isMaster) {
+ this.sessionMonitorRef = await this.launchMonitor();
+ } else {
+ this.serverWorkerRef = await this.launchServerWorker();
+ }
} else {
throw new Error("Cannot launch a session thread more than once per process.");
}
}
- protected abstract async launchImplementation(): Promise<void>;
+ protected abstract async launchMonitor(): Promise<Session.Monitor>;
+ protected abstract async launchServerWorker(): Promise<Session.ServerWorker>;
}
@@ -92,11 +90,14 @@ export namespace Session {
setPort: (port: "server" | "socket" | string, value: number, immediateRestart: boolean) => void;
killSession: (graceful?: boolean) => never;
addReplCommand: (basename: string, argPatterns: (RegExp | string)[], action: ReplAction) => void;
- addChildMessageHandler: (message: string, handler: ActionHandler) => void;
+ addServerMessageListener: (message: string, handler: ActionHandler) => void;
+ removeServerMessageListener: (message: string, handler: ActionHandler) => void;
+ clearServerMessageListeners: (message: string) => void;
}
export interface ServerWorker {
- killSession: () => void;
+ killSession: (graceful?: boolean) => void;
+ sendSessionAction: (message: string, args?: any) => void;
addExitHandler: (handler: ExitHandler) => void;
}
@@ -176,7 +177,7 @@ export namespace Session {
export async function initializeMonitorThread(notifiers?: MonitorNotifierHooks): Promise<Monitor> {
console.log(timestamp(), cyan("initializing session..."));
let activeWorker: Worker;
- const childMessageHandlers: { [message: string]: ActionHandler } = {};
+ const onMessage: { [message: string]: ActionHandler[] | undefined } = {};
// read in configuration .json file only once, in the master thread
// pass down any variables the pertinent to the child processes as environment variables
@@ -275,7 +276,7 @@ export namespace Session {
switch (message) {
case "kill":
log(red("an authorized user has manually ended the server session"));
- killSession();
+ killSession(args.graceful);
case "notify_crash":
if (notifiers && notifiers.crash) {
const { error } = args;
@@ -287,9 +288,9 @@ export namespace Session {
const { port, value, immediateRestart } = args;
setPort(port, value, immediateRestart);
default:
- const handler = childMessageHandlers[message];
- if (handler) {
- handler({ message, args });
+ const handlers = onMessage[message];
+ if (handlers) {
+ handlers.forEach(handler => handler({ message, args }));
}
}
} else if (lifecycle) {
@@ -333,7 +334,24 @@ export namespace Session {
// returned to allow the caller to add custom commands
return {
addReplCommand: repl.registerCommand,
- addChildMessageHandler: (message: string, handler: ActionHandler) => { childMessageHandlers[message] = handler; },
+ addServerMessageListener: (message: string, handler: ActionHandler) => {
+ const handlers = onMessage[message];
+ if (handlers) {
+ handlers.push(handler);
+ } else {
+ onMessage[message] = [handler];
+ }
+ },
+ removeServerMessageListener: (message: string, handler: ActionHandler) => {
+ const handlers = onMessage[message];
+ if (handlers) {
+ const index = handlers.indexOf(handler);
+ if (index > -1) {
+ handlers.splice(index, 1);
+ }
+ }
+ },
+ clearServerMessageListeners: (message: string) => onMessage[message] = undefined,
restartServer,
killSession,
setPort,
@@ -431,7 +449,8 @@ export namespace Session {
return {
addExitHandler: (handler: ExitHandler) => exitHandlers.push(handler),
- killSession: () => process.send!({ action: { message: "kill" } })
+ killSession: (graceful = true) => process.send!({ action: { message: "kill", args: { graceful } } }),
+ sendSessionAction: (message: string, args?: any) => process.send!({ action: { message, args } })
};
}
diff --git a/src/server/index.ts b/src/server/index.ts
index f18e9abb6..ffab0f380 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -92,7 +92,7 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }:
secureHandler: ({ req, res }) => {
if (req.params.key === process.env.session_key) {
res.send("<img src='https://media.giphy.com/media/NGIfqtcS81qi4/giphy.gif' style='width:100%;height:100%;'/>");
- sessionAgent.serverWorker.killSession();
+ sessionAgent.killSession();
} else {
res.redirect("/home");
}