aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/server/DashSession.ts27
-rw-r--r--src/server/Session/session.ts55
-rw-r--r--src/server/repl.ts9
3 files changed, 60 insertions, 31 deletions
diff --git a/src/server/DashSession.ts b/src/server/DashSession.ts
index 47a63c64f..7a1547e2f 100644
--- a/src/server/DashSession.ts
+++ b/src/server/DashSession.ts
@@ -1,8 +1,7 @@
import { Session } from "./Session/session";
import { Email } from "./ActionUtilities";
-import { red, yellow, cyan } from "colors";
-import { SolrManager } from "./ApiManagers/SearchManager";
-import { exec } from "child_process";
+import { red, yellow } from "colors";
+import { get } from "request-promise";
import { Utils } from "../Utils";
import { WebSocket } from "./Websocket/Websocket";
import { MessageStore } from "./Message";
@@ -26,7 +25,7 @@ export class DashSessionAgent extends Session.AppliedSessionAgent {
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 } }) => monitor.log(red(`dispatch failure @ ${recipient} (${yellow(message)})`)));
+ failures.map(({ recipient, error: { message } }) => monitor.mainLog(red(`dispatch failure @ ${recipient} (${yellow(message)})`)));
return false;
}
return true;
@@ -42,21 +41,23 @@ export class DashSessionAgent extends Session.AppliedSessionAgent {
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 } }) => monitor.log(red(`dispatch failure @ ${recipient} (${yellow(message)})`)));
+ failures.map(({ recipient, error: { message } }) => monitor.mainLog(red(`dispatch failure @ ${recipient} (${yellow(message)})`)));
return false;
}
return true;
}
});
- monitor.addReplCommand("pull", [], () => exec("git pull", (error, stdout, stderr) => {
- if (error) {
- monitor.log(red("unable to pull from version control"));
- monitor.log(red(error.message));
+ monitor.addReplCommand("pull", [], () => monitor.exec("git pull"));
+ monitor.addReplCommand("solr", [/start|stop/], async args => {
+ const command = args[0] === "start" ? "start" : "stop -p 8983";
+ await monitor.exec(command, { cwd: "./solr-8.3.1/bin" });
+ try {
+ await get("http://localhost:8983");
+ return true;
+ } catch {
+ return false;
}
- stdout.split("\n").forEach(line => line.length && monitor.execLog(cyan(line)));
- stderr.split("\n").forEach(line => line.length && monitor.execLog(yellow(line)));
- }));
- monitor.addReplCommand("solr", [/start|stop/], args => SolrManager.SetRunning(args[0] === "start"));
+ });
return monitor;
}
diff --git a/src/server/Session/session.ts b/src/server/Session/session.ts
index 9a222b2eb..867d02a0f 100644
--- a/src/server/Session/session.ts
+++ b/src/server/Session/session.ts
@@ -6,6 +6,7 @@ import Repl, { ReplAction } from "../repl";
import { readFileSync } from "fs";
import { validate, ValidationError } from "jsonschema";
import { configurationSchema } from "./session_config_schema";
+import { exec, ExecOptions } from "child_process";
/**
* This namespace relies on NodeJS's cluster module, which allows a parent (master) process to share
@@ -191,8 +192,8 @@ export namespace Session {
* requests to complete) or immediately.
*/
public killSession = async (reason: string, graceful = true, errorCode = 0) => {
- this.log(cyan(`exiting session ${graceful ? "clean" : "immediate"}ly`));
- this.log(`reason: ${(red(reason))}`);
+ this.mainLog(cyan(`exiting session ${graceful ? "clean" : "immediate"}ly`));
+ this.mainLog(`reason: ${(red(reason))}`);
await this.executeExitHandlers(null);
this.tryKillActiveWorker(graceful);
process.exit(errorCode);
@@ -212,6 +213,26 @@ export namespace Session {
this.repl.registerCommand(basename, argPatterns, action);
}
+ public exec = (command: string, options?: ExecOptions) => {
+ return new Promise<void>(resolve => {
+ exec(command, { ...options, encoding: "utf8" }, (error, stdout, stderr) => {
+ if (error) {
+ this.execLog(red(`unable to execute ${white(command)}`));
+ error.message.split("\n").forEach(line => line.length && this.execLog(red(`(error) ${line}`)));
+ } else {
+ let outLines: string[], errorLines: string[];
+ if ((outLines = stdout.split("\n").filter(line => line.length)).length) {
+ outLines.forEach(line => line.length && this.execLog(cyan(`(stdout) ${line}`)));
+ }
+ if ((errorLines = stderr.split("\n").filter(line => line.length)).length) {
+ errorLines.forEach(line => line.length && this.execLog(yellow(`(stderr) ${line}`)));
+ }
+ }
+ resolve();
+ });
+ });
+ }
+
/**
* Add a listener at this message. When the monitor process
* receives a message, it will invoke all registered functions.
@@ -259,9 +280,9 @@ export namespace Session {
// to be caught in a try catch, and is inconsequential, so it is ignored
process.on("uncaughtException", ({ message, stack }): void => {
if (message !== "Channel closed") {
- this.log(red(message));
+ this.mainLog(red(message));
if (stack) {
- this.log(`uncaught exception\n${red(stack)}`);
+ this.mainLog(`uncaught exception\n${red(stack)}`);
}
}
});
@@ -269,7 +290,7 @@ export namespace Session {
// a helpful cluster event called on the master thread each time a child process exits
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}`}.`;
- this.log(cyan(prompt));
+ this.mainLog(cyan(prompt));
// to make this a robust, continuous session, every time a child process dies, we immediately spawn a new one
this.spawn();
});
@@ -287,14 +308,14 @@ export namespace Session {
/**
* A formatted, identified and timestamped log in color
*/
- public log = (...optionalParams: any[]) => {
+ public mainLog = (...optionalParams: any[]) => {
console.log(this.timestamp(), this.config.identifiers.master.text, ...optionalParams);
}
/**
* A formatted, identified and timestamped log in color for non-
*/
- public execLog = (...optionalParams: any[]) => {
+ private execLog = (...optionalParams: any[]) => {
console.log(this.timestamp(), this.config.identifiers.exec.text, ...optionalParams);
}
@@ -310,7 +331,7 @@ export namespace Session {
this.key = Utils.GenerateGuid();
const success = await this.notifiers.key(this.key);
const statement = success ? green("distributed session key to recipients") : red("distribution of session key failed");
- this.log(statement);
+ this.mainLog(statement);
}
}
@@ -394,7 +415,7 @@ export namespace Session {
repl.registerCommand("set", [/polling/, number, boolean], args => {
const newPollingIntervalSeconds = Math.floor(Number(args[2]));
if (newPollingIntervalSeconds < 0) {
- this.log(red("the polling interval must be a non-negative integer"));
+ this.mainLog(red("the polling interval must be a non-negative integer"));
} else {
if (newPollingIntervalSeconds !== this.config.polling.intervalSeconds) {
this.config.polling.intervalSeconds = newPollingIntervalSeconds;
@@ -413,11 +434,12 @@ export namespace Session {
* Attempts to kill the active worker gracefully, unless otherwise specified.
*/
private tryKillActiveWorker = (graceful = true): boolean => {
- if (!this.activeWorker?.isDead()) {
+ if (this.activeWorker && !this.activeWorker.isDead()) {
+ this.mainLog(cyan(`${graceful ? "graceful" : "immediate"}ly killing the active server worker`));
if (graceful) {
- this.activeWorker?.send({ manualExit: true });
+ this.activeWorker.send({ manualExit: true });
} else {
- this.activeWorker?.process.kill();
+ this.activeWorker.process.kill();
}
return true;
}
@@ -438,7 +460,7 @@ export namespace Session {
this.tryKillActiveWorker();
}
} else {
- this.log(red(`${port} is an invalid port number`));
+ this.mainLog(red(`${port} is an invalid port number`));
}
}
@@ -464,7 +486,7 @@ export namespace Session {
pollingIntervalSeconds: intervalSeconds,
session_key: this.key
});
- this.log(cyan(`spawned new server worker with process id ${this.activeWorker.process.pid}`));
+ this.mainLog(cyan(`spawned new server worker with process id ${this.activeWorker.process.pid}`));
// an IPC message handler that executes actions on the master thread when prompted by the active worker
this.activeWorker.on("message", async ({ lifecycle, action }) => {
if (action) {
@@ -480,7 +502,7 @@ export namespace Session {
const { error } = args;
const success = await this.notifiers.crash(error);
const statement = success ? green("distributed crash notification to recipients") : red("distribution of crash notification failed");
- this.log(statement);
+ this.mainLog(statement);
}
break;
case "set_port":
@@ -492,7 +514,8 @@ export namespace Session {
if (handlers) {
handlers.forEach(handler => handler({ message, args }));
}
- } else if (lifecycle) {
+ }
+ if (lifecycle) {
console.log(this.timestamp(), `${this.config.identifiers.worker.text} lifecycle phase (${lifecycle})`);
}
});
diff --git a/src/server/repl.ts b/src/server/repl.ts
index c4526528e..ad55b6aaa 100644
--- a/src/server/repl.ts
+++ b/src/server/repl.ts
@@ -109,8 +109,13 @@ export default class Repl {
}
}
if (!length || matched) {
- await action(parsed);
- this.valid(`${command} ${parsed.join(" ")}`);
+ const result = action(parsed);
+ const resolve = () => this.valid(`${command} ${parsed.join(" ")}`);
+ if (result instanceof Promise) {
+ result.then(resolve);
+ } else {
+ resolve();
+ }
return;
}
}