From 4ed6fd76752d60c617d9a396193ab6d8195dac1c Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 10 Dec 2019 21:08:39 -0500 Subject: persistence daemon improvement, relaxed dummy gmail account security to allow messages --- src/server/ActionUtilities.ts | 6 +----- src/server/ApiManagers/SearchManager.ts | 2 +- src/server/ApiManagers/UtilManager.ts | 2 +- src/server/index.ts | 25 ------------------------- src/server/persistence_daemon.ts | 11 +++++++---- src/server/updateSearch.ts | 13 ++++++++++--- 6 files changed, 20 insertions(+), 39 deletions(-) (limited to 'src/server') diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts index 53ddea2fc..bc978c982 100644 --- a/src/server/ActionUtilities.ts +++ b/src/server/ActionUtilities.ts @@ -51,11 +51,7 @@ export async function log_execution({ startMessage, endMessage, action, color } catch (e) { error = e; } finally { - if (typeof endMessage === "string") { - log_helper(`${endMessage}.`, resolvedColor); - } else { - log_helper(`${endMessage({ result, error })}.`, resolvedColor); - } + log_helper(`${typeof endMessage === "string" ? endMessage : endMessage({ result, error })}.`, resolvedColor); } return result; } diff --git a/src/server/ApiManagers/SearchManager.ts b/src/server/ApiManagers/SearchManager.ts index ccfd570b8..37d66666b 100644 --- a/src/server/ApiManagers/SearchManager.ts +++ b/src/server/ApiManagers/SearchManager.ts @@ -72,7 +72,7 @@ export namespace SolrManager { const args = status ? "start" : "stop -p 8983"; try { console.log(`Solr management: trying to ${args}`); - console.log(await command_line(`solr.cmd ${args}`, "../../solr-8.1.1/bin")); + console.log(await command_line(`solr.cmd ${args}`, "./solr-8.3.1/bin")); return true; } catch (e) { console.log(red(`Solr management error: unable to ${args}`)); diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts index e959645e0..2f1bd956f 100644 --- a/src/server/ApiManagers/UtilManager.ts +++ b/src/server/ApiManagers/UtilManager.ts @@ -43,7 +43,7 @@ export default class UtilManager extends ApiManager { method: Method.GET, subscription: "/buxton", onValidation: async ({ res }) => { - const cwd = '../scraping/buxton'; + const cwd = './src/scraping/buxton'; const onResolved = (stdout: string) => { console.log(stdout); res.redirect("/"); }; const onRejected = (err: any) => { console.error(err.message); res.send(err); }; diff --git a/src/server/index.ts b/src/server/index.ts index 3764eaabb..7671936a2 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -119,31 +119,6 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: } }); - let daemonInitialized = false; - addSupervisedRoute({ - method: Method.GET, - subscription: "/persist", - onValidation: async ({ res }) => { - if (!daemonInitialized) { - daemonInitialized = true; - log_execution({ - startMessage: "\ninitializing persistence daemon", - endMessage: ({ result, error }) => { - const success = error === null && result !== undefined; - if (!success) { - console.log(red("failed to initialize the persistance daemon")); - process.exit(0); - } - return "persistence daemon process closed"; - }, - action: async () => command_line("npx ts-node ./persistence_daemon.ts", "./src/server"), - color: yellow - }); - } - res.redirect("/home"); - } - }); - logRegistrationOutcome(); // initialize the web socket (bidirectional communication: if a user changes diff --git a/src/server/persistence_daemon.ts b/src/server/persistence_daemon.ts index 388440b49..2cb17456c 100644 --- a/src/server/persistence_daemon.ts +++ b/src/server/persistence_daemon.ts @@ -3,8 +3,9 @@ import { command_line, log_execution } from "./ActionUtilities"; import { red, yellow, cyan, green } from "colors"; import * as nodemailer from "nodemailer"; import { MailOptions } from "nodemailer/lib/json-transport"; +import { Database } from "./database"; -const { LOCATION } = process.env; +const LOCATION = "http://localhost"; const recipient = "samuel_wilkins@brown.edu"; let restarting = false; @@ -39,13 +40,15 @@ async function listen() { console.log(await log_execution({ startMessage: "Initiating server restart", endMessage: "Server successfully restarted", - action: async () => command_line(`npm run start${suffix}`, "../../"), + action: () => command_line(`npm run start${suffix}`), color: green })); restarting = false; + } else { + console.log(green(`No issues detected as of ${new Date().toISOString()}`)); } } - }, 1000 * 90); + }, 1000 * 10); } function emailText(error: any) { @@ -72,7 +75,7 @@ async function notify(error: any) { text: emailText(error) } as MailOptions; return new Promise(resolve => { - smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); + smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => { console.log(dispatchError); resolve(dispatchError === null); }); }); } diff --git a/src/server/updateSearch.ts b/src/server/updateSearch.ts index 5ae6885c5..83094d36a 100644 --- a/src/server/updateSearch.ts +++ b/src/server/updateSearch.ts @@ -59,7 +59,14 @@ async function update() { }); const cursor = await log_execution({ startMessage: "Connecting to and querying for all documents from database...", - endMessage: "Connection successful and query complete", + endMessage: ({ result, error }) => { + const success = error === null && result !== undefined; + if (!success) { + console.log(red("Unable to connect to the database.")); + process.exit(0); + } + return "Connection successful and query complete"; + }, action: () => Database.Instance.query({}), color: yellow }); @@ -92,7 +99,7 @@ async function update() { updates.push(update); } } - await cursor.forEach(updateDoc); + await cursor?.forEach(updateDoc); const result = await log_execution({ startMessage: `Dispatching updates for ${updates.length} documents`, endMessage: "Dispatched updates complete", @@ -107,7 +114,7 @@ async function update() { console.log(result); console.log("\n"); } - await cursor.close(); + await cursor?.close(); process.exit(0); } -- cgit v1.2.3-70-g09d2 From 6665483b80ff6874cf9cc7c9cb3f7e58fcec20ca Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 10 Dec 2019 23:40:42 -0500 Subject: persistance daemon improvements --- ...npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT | 0 package.json | 3 +- src/server/ActionUtilities.ts | 13 ++- src/server/ProcessManager.ts | 45 +++++++++++ src/server/daemon/persistence_daemon.ts | 92 ++++++++++++++++++++++ src/server/index.ts | 13 ++- src/server/persistence_daemon.ts | 82 ------------------- 7 files changed, 163 insertions(+), 85 deletions(-) create mode 100644 logs/npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT create mode 100644 src/server/ProcessManager.ts create mode 100644 src/server/daemon/persistence_daemon.ts delete mode 100644 src/server/persistence_daemon.ts (limited to 'src/server') diff --git a/logs/npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT b/logs/npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT new file mode 100644 index 000000000..e69de29bb diff --git a/package.json b/package.json index 1689194db..97563e137 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "index.js", "scripts": { "start-release": "cross-env RELEASE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", - "start": "cross-env LOCATION=http://localhost NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", + "start-spawn": "cross-env SPAWNED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", + "start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", "debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --inspect -- src/server/index.ts", "build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production", "test": "mocha -r ts-node/register test/**/*.ts", diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts index bc978c982..2173f4369 100644 --- a/src/server/ActionUtilities.ts +++ b/src/server/ActionUtilities.ts @@ -1,12 +1,16 @@ import * as fs from 'fs'; import { ExecOptions } from 'shelljs'; -import { exec } from 'child_process'; +import { exec, spawn } from 'child_process'; import * as path from 'path'; import * as rimraf from "rimraf"; import { yellow, Color } from 'colors'; const projectRoot = path.resolve(__dirname, "../../"); +export function pathFromRoot(relative: string) { + return path.resolve(projectRoot, relative); +} + export const command_line = (command: string, fromDirectory?: string) => { return new Promise((resolve, reject) => { const options: ExecOptions = {}; @@ -17,6 +21,13 @@ export const command_line = (command: string, fromDirectory?: string) => { }); }; +export async function spawn_detached_process(command: string, args?: readonly string[]) { + const out = path.resolve(projectRoot, `./logs/${command}-${process.pid}-${new Date().toUTCString()}`); + const child_out = fs.openSync(out, 'a'); + const child_error = fs.openSync(out, 'a'); + spawn(command, args, { detached: true, stdio: ["ignore", child_out, child_error] }).unref(); +} + export const read_text_file = (relativePath: string) => { const target = path.resolve(__dirname, relativePath); return new Promise((resolve, reject) => { diff --git a/src/server/ProcessManager.ts b/src/server/ProcessManager.ts new file mode 100644 index 000000000..2237f9e1b --- /dev/null +++ b/src/server/ProcessManager.ts @@ -0,0 +1,45 @@ +import { writeFileSync, unlinkSync, existsSync, mkdirSync } from "fs"; +import { pathFromRoot, log_execution, spawn_detached_process } from './ActionUtilities'; +import { resolve } from "path"; +import { red, yellow } from "colors"; + +const daemonPath = pathFromRoot("./src/server/daemon/persistence_daemon.ts"); + +export namespace ProcessManager { + + export async function initialize() { + const logPath = pathFromRoot("./logs"); + const filePath = resolve(logPath, "./server_pids.txt"); + const exists = existsSync(logPath); + if (exists) { + unlinkSync(filePath); + } else { + mkdirSync(logPath); + } + const { pid } = process; + if (process.env.SPAWNED === "true") { + writeFileSync(filePath, `${pid} created at ${new Date().toUTCString()}\n`); + } + } + + let daemonInitialized = false; + export async function trySpawnDaemon() { + if (!daemonInitialized) { + daemonInitialized = true; + await log_execution({ + startMessage: "\ninitializing persistence daemon", + endMessage: ({ result, error }) => { + const success = error === null && result !== undefined; + if (!success) { + console.log(red("failed to initialize the persistance daemon")); + process.exit(0); + } + return "persistence daemon process closed"; + }, + action: () => spawn_detached_process("npx ts-node", [daemonPath]), + color: yellow + }); + } + } + +} \ No newline at end of file diff --git a/src/server/daemon/persistence_daemon.ts b/src/server/daemon/persistence_daemon.ts new file mode 100644 index 000000000..3eb17a9b4 --- /dev/null +++ b/src/server/daemon/persistence_daemon.ts @@ -0,0 +1,92 @@ +import * as request from "request-promise"; +import { log_execution, spawn_detached_process } from "../ActionUtilities"; +import { red, yellow, cyan, green } from "colors"; +import * as nodemailer from "nodemailer"; +import { MailOptions } from "nodemailer/lib/json-transport"; +import { writeFileSync } from "fs"; +import { resolve } from 'path'; + +const LOCATION = "http://localhost"; +const recipient = "samuel_wilkins@brown.edu"; +let restarting = false; + +writeFileSync(resolve(__dirname, "./current_pid.txt"), process.pid); + +function timestamp() { + return `@ ${new Date().toISOString()}`; +} + +async function listen() { + if (!LOCATION) { + console.log(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); + } catch (e) { + error = e; + } finally { + if (error) { + if (!restarting) { + restarting = true; + console.log(yellow("Detected a server crash!")); + await log_execution({ + startMessage: "Sending crash notification email", + endMessage: ({ error, result }) => { + const success = error === null && result === true; + return (success ? `Notification successfully sent to ` : `Failed to notify `) + recipient; + }, + action: async () => notify(error || "Hmm, no error to report..."), + color: cyan + }); + console.log(await log_execution({ + startMessage: "Initiating server restart", + endMessage: "Server successfully restarted", + action: () => spawn_detached_process(`npm run start-spawn`), + color: green + })); + restarting = false; + } else { + console.log(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); + } + } else { + console.log(green(`No issues detected ${timestamp()}`)); + } + } + }, 1000 * 10); +} + +function emailText(error: any) { + return [ + `Hey ${recipient.split("@")[0]},`, + "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", + `Location: ${LOCATION}\nError: ${error}`, + "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress." + ].join("\n\n"); +} + +async function notify(error: any) { + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'brownptcdash@gmail.com', + pass: 'browngfx1' + } + }); + const mailOptions = { + to: recipient, + from: 'brownptcdash@gmail.com', + subject: 'Dash Server Crash', + text: emailText(error) + } as MailOptions; + return new Promise(resolve => { + smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => { console.log(dispatchError); resolve(dispatchError === null); }); + }); +} + +listen(); \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 7671936a2..795418b31 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -18,11 +18,12 @@ import { GoogleCredentialsLoader } from './credentials/CredentialsLoader'; import DeleteManager from "./ApiManagers/DeleteManager"; import PDFManager from "./ApiManagers/PDFManager"; import UploadManager from "./ApiManagers/UploadManager"; -import { log_execution, command_line } from "./ActionUtilities"; +import { log_execution } from "./ActionUtilities"; import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager"; import GooglePhotosManager from "./ApiManagers/GooglePhotosManager"; import { yellow, red } from "colors"; import { disconnect } from "../server/Initialization"; +import { ProcessManager } from "./ProcessManager"; export const publicDirectory = path.resolve(__dirname, "public"); export const filesDirectory = path.resolve(publicDirectory, "files"); @@ -35,6 +36,7 @@ export const ExitHandlers = new Array<() => void>(); * before clients can access the server should be run or awaited here. */ async function preliminaryFunctions() { + await ProcessManager.initialize(); await GoogleCredentialsLoader.loadCredentials(); GoogleApiServerUtils.processProjectCredentials(); await DashUploadUtils.buildFileDirectories(); @@ -119,6 +121,15 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: } }); + addSupervisedRoute({ + method: Method.GET, + subscription: "/persist", + onValidation: ({ res }) => { + ProcessManager.trySpawnDaemon(); + res.redirect("/home"); + } + }); + logRegistrationOutcome(); // initialize the web socket (bidirectional communication: if a user changes diff --git a/src/server/persistence_daemon.ts b/src/server/persistence_daemon.ts deleted file mode 100644 index 2cb17456c..000000000 --- a/src/server/persistence_daemon.ts +++ /dev/null @@ -1,82 +0,0 @@ -import * as request from "request-promise"; -import { command_line, log_execution } from "./ActionUtilities"; -import { red, yellow, cyan, green } from "colors"; -import * as nodemailer from "nodemailer"; -import { MailOptions } from "nodemailer/lib/json-transport"; -import { Database } from "./database"; - -const LOCATION = "http://localhost"; -const recipient = "samuel_wilkins@brown.edu"; -let restarting = false; - -async function listen() { - if (!LOCATION) { - console.log(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 response: any; - let error: any; - try { - response = await request.get(heartbeat); - } catch (e) { - error = e; - } finally { - if (!response && !restarting) { - restarting = true; - console.log(yellow("Detected a server crash!")); - await log_execution({ - startMessage: "Sending crash notification email", - endMessage: ({ error, result }) => { - const success = error === null && result === true; - return (success ? `Notification successfully sent to ` : `Failed to notify `) + recipient; - }, - action: async () => notify(error || "Hmm, no error to report..."), - color: cyan - }); - console.log(await log_execution({ - startMessage: "Initiating server restart", - endMessage: "Server successfully restarted", - action: () => command_line(`npm run start${suffix}`), - color: green - })); - restarting = false; - } else { - console.log(green(`No issues detected as of ${new Date().toISOString()}`)); - } - } - }, 1000 * 10); -} - -function emailText(error: any) { - return [ - `Hey ${recipient.split("@")[0]},`, - "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", - `Location: ${LOCATION}\nError: ${error}`, - "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress." - ].join("\n\n"); -} - -async function notify(error: any) { - const smtpTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'brownptcdash@gmail.com', - pass: 'browngfx1' - } - }); - const mailOptions = { - to: recipient, - from: 'brownptcdash@gmail.com', - subject: 'Dash Server Crash', - text: emailText(error) - } as MailOptions; - return new Promise(resolve => { - smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => { console.log(dispatchError); resolve(dispatchError === null); }); - }); -} - -listen(); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From ad079a088ae9262a4a40a2f0d2a2c5d948140492 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 11 Dec 2019 03:57:09 -0500 Subject: somewhat functional daemon --- logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log | 274 +++++++++++++++++++++ ...npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT | 0 logs/server_pids.txt | 1 + src/server/ActionUtilities.ts | 22 +- src/server/ProcessManager.ts | 49 ++-- src/server/daemon/current_daemon_pid.txt | 1 + src/server/daemon/persistence_daemon.ts | 53 ++-- .../session_crashes_@ 2019-12-11T08:31:56.281Z.log | 1 + .../session_crashes_@ 2019-12-11T08:43:46.454Z.log | 2 + 9 files changed, 357 insertions(+), 46 deletions(-) create mode 100644 logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log delete mode 100644 logs/npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT create mode 100644 logs/server_pids.txt create mode 100644 src/server/daemon/current_daemon_pid.txt create mode 100644 src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log create mode 100644 src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log (limited to 'src/server') diff --git a/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log b/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log new file mode 100644 index 000000000..37e232d48 --- /dev/null +++ b/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log @@ -0,0 +1,274 @@ + +> dash@1.0.0 start-spawn /Users/swilkinss2012/Documents/GitHub/Dash-Web +> cross-env SPAWNED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts + +Using ts-node version 7.0.1, typescript version 3.7.2 +objc[9678]: Class GNotificationCenterDelegate is implemented in both /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/sharp/vendor/lib/libgio-2.0.0.dylib (0x10838d578) and /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/canvas/build/Release/libgio-2.0.0.dylib (0x10afd9578). One of the two will be used. Which one is undefined. + +starting execution of preliminary functions... +(node:9678) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect. +completed preliminary functions +. +Starting type checking and linting service... +Using 1 worker with 2048MB memory limit + +running server in development mode + +registering server routes... +all server routes have been successfully registered: +/ +/activity +/buxton +/delete +/deleteAll +/deleteWithAux +/deleteWithGoogleCredentials +/doc/:docId +/downloadId/:docId +/environment/:key +/getCurrentUser +/getUserDocumentId +/getUsers +/googleDocs/:sector/:action +/googlePhotosMediaDownload +/googlePhotosMediaUpload +/home +/imageHierarchyExport/:docId +/inspectImage +/persist +/pull +/readGoogleAccessToken +/search +/serializeDoc/:docId +/serverHeartbeat +/shutdown +/solr/:action +/textsearch +/thumbnail/:filename +/upload +/uploadDoc +/uploadURI +/version +/writeGoogleAccessToken + +websocket listening on port 4321 +server listening on port 1050 + +ℹ 「wdm」: wait until bundle finished: /serverHeartbeat +user samuel_wilkins@brown.edu has connected to the web socket +user samuel_wilkins@brown.edu has connected to the web socket +ℹ 「wdm」: wait until bundle finished: /serverHeartbeat +ℹ 「wdm」: wait until bundle finished: /serverHeartbeat +ℹ 「wdm」: wait until bundle finished: /serverHeartbeat +ℹ 「wdm」: wait until bundle finished: /serverHeartbeat +user samuel_wilkins@brown.edu has connected to the web socket +user samuel_wilkins@brown.edu has connected to the web socket +Type checking and linting in progress... +webpack built 8f6b743d91fd3862683b in 47419ms +⚠ 「wdm」: Hash: 8f6b743d91fd3862683b +Version: webpack 4.36.1 +Time: 47419ms +Built at: 12/11/2019 3:45:31 AM + Asset Size Chunks  Chunk Names +275711e56bd1bc79fdff544a3d7dbfae.png 289 bytes  [emitted] +32f1593298e6e7bee5673bf647328d72.png 429 bytes  [emitted] +718c914a99a2136c01c84e01f63e505a.png 829 bytes  [emitted] +906f1a1816c2a03b5c7612f6aa2ceece.png 281 bytes  [emitted] + assets/downarrow.png 3.28 KiB  [emitted] + assets/env.json 360 bytes  [emitted] + assets/google_photos.png 114 KiB  [emitted] + assets/google_tags.png 7.9 KiB  [emitted] + assets/loading.gif 112 KiB  [emitted] + assets/pdf.worker.js 1.55 MiB  [emitted] + bundle.js 20.8 MiB bundle [emitted] bundle + bundle.js.map 23.3 MiB bundle [emitted] bundle + debug/repl.html 348 bytes  [emitted] + debug/test.html 245 bytes  [emitted] + debug/viewer.html 357 bytes  [emitted] +e7a34b49f3c49ca0c25c76b30cd09e12.png 445 bytes  [emitted] + imageUpload.js 20.8 MiB imageUpload [emitted] imageUpload + imageUpload.js.map 23.3 MiB imageUpload [emitted] imageUpload + index.html 593 bytes  [emitted] + inkControls.js 116 KiB inkControls [emitted] inkControls + inkControls.js.map 122 KiB inkControls [emitted] inkControls + mobile/image.html 333 bytes  [emitted] + mobile/ink.html 252 bytes  [emitted] + repl.js 9.31 MiB repl [emitted] repl + repl.js.map 10.5 MiB repl [emitted] repl + test.js 1.2 MiB test [emitted] test + test.js.map 1.42 MiB test [emitted] test + test.pdf 53.6 KiB  [emitted] + vendors~pdfjsWorker.js 1.55 MiB vendors~pdfjsWorker [emitted] vendors~pdfjsWorker + vendors~pdfjsWorker.js.map 1.87 MiB vendors~pdfjsWorker [emitted] vendors~pdfjsWorker + viewer.js 9.47 MiB viewer [emitted] viewer + viewer.js.map 10.7 MiB viewer [emitted] viewer +Entrypoint bundle = bundle.js bundle.js.map +Entrypoint viewer = viewer.js viewer.js.map +Entrypoint repl = repl.js repl.js.map +Entrypoint test = test.js test.js.map +Entrypoint inkControls = inkControls.js inkControls.js.map +Entrypoint imageUpload = imageUpload.js imageUpload.js.map +[19] multi ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true 40 bytes {bundle} [built] +[20] multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true 40 bytes {viewer} [built] +[21] multi ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true 40 bytes {repl} [built] +[22] multi ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true 40 bytes {test} [built] +[23] multi ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true 40 bytes {inkControls} [built] +[24] multi ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true 40 bytes {imageUpload} [built] + [./node_modules/mobx-react/index.module.js] 48.8 KiB {bundle} {viewer} {repl} {imageUpload} [built] + [./node_modules/mobx/lib/mobx.module.js] 175 KiB {bundle} {viewer} {repl} {imageUpload} [built] + [./node_modules/webpack-hot-middleware/client.js?reload=true] (webpack)-hot-middleware/client.js?reload=true 7.68 KiB {bundle} {viewer} {repl} {test} {inkControls} {imageUpload} [built] + [./src/client/views/Main.tsx] 4.03 KiB {bundle} [built] + [./src/debug/Repl.tsx] 6.87 KiB {repl} [built] + [./src/debug/Test.tsx] 1.02 KiB {test} [built] + [./src/debug/Viewer.tsx] 12.1 KiB {viewer} [built] + [./src/mobile/ImageUpload.tsx] 9.97 KiB {imageUpload} [built] + [./src/mobile/InkControls.tsx] 14 bytes {inkControls} [built] + + 1494 hidden modules + +WARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60 +Critical dependency: the request of a dependency is an expression + @ ./src/client/util/Scripting.ts + @ ./src/debug/Viewer.tsx + @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true +ℹ 「wdm」: Compiled with warnings. +ℹ 「wdm」: Compiling... +webpack building... +ℹ 「wdm」: wait until bundle finished: /login +ℹ 「wdm」: wait until bundle finished: /login +ℹ 「wdm」: wait until bundle finished: /login +ℹ 「wdm」: wait until bundle finished: /login +ℹ 「wdm」: wait until bundle finished: /login +Type checking and linting in progress... +webpack built 8f6b743d91fd3862683b in 615ms +⚠ 「wdm」: Hash: 8f6b743d91fd3862683b +Version: webpack 4.36.1 +Time: 615ms +Built at: 12/11/2019 3:45:33 AM + Asset Size Chunks Chunk Names +275711e56bd1bc79fdff544a3d7dbfae.png 289 bytes   +32f1593298e6e7bee5673bf647328d72.png 429 bytes   +718c914a99a2136c01c84e01f63e505a.png 829 bytes   +906f1a1816c2a03b5c7612f6aa2ceece.png 281 bytes   + bundle.js 20.8 MiB bundle bundle + bundle.js.map 23.3 MiB bundle bundle +e7a34b49f3c49ca0c25c76b30cd09e12.png 445 bytes   + imageUpload.js 20.8 MiB imageUpload imageUpload + imageUpload.js.map 23.3 MiB imageUpload imageUpload + inkControls.js 116 KiB inkControls inkControls + inkControls.js.map 122 KiB inkControls inkControls + repl.js 9.31 MiB repl repl + repl.js.map 10.5 MiB repl repl + test.js 1.2 MiB test test + test.js.map 1.42 MiB test test + vendors~pdfjsWorker.js 1.55 MiB vendors~pdfjsWorker vendors~pdfjsWorker + vendors~pdfjsWorker.js.map 1.87 MiB vendors~pdfjsWorker vendors~pdfjsWorker + viewer.js 9.47 MiB viewer viewer + viewer.js.map 10.7 MiB viewer viewer +Entrypoint bundle = bundle.js bundle.js.map +Entrypoint viewer = viewer.js viewer.js.map +Entrypoint repl = repl.js repl.js.map +Entrypoint test = test.js test.js.map +Entrypoint inkControls = inkControls.js inkControls.js.map +Entrypoint imageUpload = imageUpload.js imageUpload.js.map +[19] multi ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true 40 bytes {bundle} +[20] multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true 40 bytes {viewer} +[21] multi ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true 40 bytes {repl} +[22] multi ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true 40 bytes {test} +[23] multi ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true 40 bytes {inkControls} +[24] multi ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true 40 bytes {imageUpload} + [./node_modules/mobx-react/index.module.js] 48.8 KiB {bundle} {viewer} {repl} {imageUpload} + [./node_modules/mobx/lib/mobx.module.js] 175 KiB {bundle} {viewer} {repl} {imageUpload} + [./node_modules/webpack-hot-middleware/client.js?reload=true] (webpack)-hot-middleware/client.js?reload=true 7.68 KiB {bundle} {viewer} {repl} {test} {inkControls} {imageUpload} + [./src/client/views/Main.tsx] 4.03 KiB {bundle} + [./src/debug/Repl.tsx] 6.87 KiB {repl} + [./src/debug/Test.tsx] 1.02 KiB {test} + [./src/debug/Viewer.tsx] 12.1 KiB {viewer} + [./src/mobile/ImageUpload.tsx] 9.97 KiB {imageUpload} + [./src/mobile/InkControls.tsx] 14 bytes {inkControls} + + 1494 hidden modules + +WARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60 +Critical dependency: the request of a dependency is an expression + @ ./src/client/util/Scripting.ts + @ ./src/debug/Viewer.tsx + @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true +ℹ 「wdm」: Compiled with warnings. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(81,21): +prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(83,25): +prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(86,21): +prefer-const: Identifier 'isValidColor' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(87,25): +prefer-const: Identifier 's' is never reassigned; use 'const' instead of 'var'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(89,36): +triple-equals: == should be === +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(90,18): +semicolon: Missing semicolon +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(99,21): +prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(100,21): +prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(126,25): +prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(141,25): +prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(154,66): +no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,25): +prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,33): +no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(160,25): +prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(192,29): +prefer-const: Identifier 'doc' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(640,134): +semicolon: Missing semicolon +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(649,21): +prefer-const: Identifier 'expand' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(650,21): +prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(654,138): +semicolon: Missing semicolon +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(658,10): +semicolon: Missing semicolon +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(243,13): +prefer-const: Identifier 'main' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(244,13): +prefer-const: Identifier 'next' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(245,13): +prefer-const: Identifier 'prev' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(88,21): +prefer-const: Identifier 'selectionTitleFieldKey' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(95,8): +semicolon: Missing semicolon +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/EditableView.tsx(123,13): +prefer-const: Identifier 'wasFocused' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/MainView.tsx(289,11): +semicolon: Missing semicolon +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentView.tsx(151,25): +prefer-const: Identifier 'any' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(910,13): +prefer-const: Identifier 'prosediv' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(911,13): +prefer-const: Identifier 'keeplocation' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(913,13): +prefer-const: Identifier 'pos' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(928,17): +prefer-const: Identifier 'pcords' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(929,17): +prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(935,21): +prefer-const: Identifier 'lastNode' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(976,71): +semicolon: Missing semicolon +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(987,17): +prefer-const: Identifier '$pos' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1001,25): +prefer-const: Identifier '$olist_pos' is never reassigned; use 'const' instead of 'let'. +WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1103,17): +prefer-const: Identifier 'newHeight' is never reassigned; use 'const' instead of 'let'. +No type errors found +Version: typescript 3.7.2, tslint 5.18.0 +Time: 5473ms diff --git a/logs/npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT b/logs/npx ts-node-20965-Wed, 11 Dec 2019 03:38:32 GMT deleted file mode 100644 index e69de29bb..000000000 diff --git a/logs/server_pids.txt b/logs/server_pids.txt new file mode 100644 index 000000000..2aa143f24 --- /dev/null +++ b/logs/server_pids.txt @@ -0,0 +1 @@ +9675 created at Wed, 11 Dec 2019 08:44:28 GMT diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts index 2173f4369..9bdc4ed93 100644 --- a/src/server/ActionUtilities.ts +++ b/src/server/ActionUtilities.ts @@ -1,12 +1,11 @@ -import * as fs from 'fs'; +import { readFile, writeFile, exists, mkdir, unlink } from 'fs'; import { ExecOptions } from 'shelljs'; -import { exec, spawn } from 'child_process'; +import { exec } from 'child_process'; import * as path from 'path'; import * as rimraf from "rimraf"; import { yellow, Color } from 'colors'; const projectRoot = path.resolve(__dirname, "../../"); - export function pathFromRoot(relative: string) { return path.resolve(projectRoot, relative); } @@ -21,24 +20,17 @@ export const command_line = (command: string, fromDirectory?: string) => { }); }; -export async function spawn_detached_process(command: string, args?: readonly string[]) { - const out = path.resolve(projectRoot, `./logs/${command}-${process.pid}-${new Date().toUTCString()}`); - const child_out = fs.openSync(out, 'a'); - const child_error = fs.openSync(out, 'a'); - spawn(command, args, { detached: true, stdio: ["ignore", child_out, child_error] }).unref(); -} - export const read_text_file = (relativePath: string) => { const target = path.resolve(__dirname, relativePath); return new Promise((resolve, reject) => { - fs.readFile(target, (err, data) => err ? reject(err) : resolve(data.toString())); + readFile(target, (err, data) => err ? reject(err) : resolve(data.toString())); }); }; export const write_text_file = (relativePath: string, contents: any) => { const target = path.resolve(__dirname, relativePath); return new Promise((resolve, reject) => { - fs.writeFile(target, contents, (err) => err ? reject(err) : resolve()); + writeFile(target, contents, (err) => err ? reject(err) : resolve()); }); }; @@ -93,10 +85,10 @@ export function msToTime(duration: number) { } export const createIfNotExists = async (path: string) => { - if (await new Promise(resolve => fs.exists(path, resolve))) { + if (await new Promise(resolve => exists(path, resolve))) { return true; } - return new Promise(resolve => fs.mkdir(path, error => resolve(error === null))); + return new Promise(resolve => mkdir(path, error => resolve(error === null))); }; export async function Prune(rootDirectory: string): Promise { @@ -104,4 +96,4 @@ export async function Prune(rootDirectory: string): Promise { return error === null; } -export const Destroy = (mediaPath: string) => new Promise(resolve => fs.unlink(mediaPath, error => resolve(error === null))); +export const Destroy = (mediaPath: string) => new Promise(resolve => unlink(mediaPath, error => resolve(error === null))); diff --git a/src/server/ProcessManager.ts b/src/server/ProcessManager.ts index 2237f9e1b..671f0a234 100644 --- a/src/server/ProcessManager.ts +++ b/src/server/ProcessManager.ts @@ -1,7 +1,9 @@ -import { writeFileSync, unlinkSync, existsSync, mkdirSync } from "fs"; -import { pathFromRoot, log_execution, spawn_detached_process } from './ActionUtilities'; -import { resolve } from "path"; -import { red, yellow } from "colors"; +import { existsSync, mkdirSync, createWriteStream } from "fs"; +import { pathFromRoot, log_execution } from './ActionUtilities'; +import { red, green } from "colors"; +import rimraf = require("rimraf"); +import { ChildProcess, spawn } from "child_process"; +import { Stream } from "stream"; const daemonPath = pathFromRoot("./src/server/daemon/persistence_daemon.ts"); @@ -9,22 +11,33 @@ export namespace ProcessManager { export async function initialize() { const logPath = pathFromRoot("./logs"); - const filePath = resolve(logPath, "./server_pids.txt"); - const exists = existsSync(logPath); - if (exists) { - unlinkSync(filePath); - } else { - mkdirSync(logPath); + if (existsSync(logPath)) { + if (!process.env.SPAWNED) { + await new Promise(resolve => rimraf(logPath, resolve)); + } } - const { pid } = process; - if (process.env.SPAWNED === "true") { - writeFileSync(filePath, `${pid} created at ${new Date().toUTCString()}\n`); + mkdirSync(logPath); + } + + function generate_log_name(command: string, args?: readonly string[]) { + return pathFromRoot(`./logs/${command}-${args?.length}-${new Date().toUTCString()}.log`); + } + + export type Sink = "pipe" | "ipc" | "ignore" | "inherit" | Stream | number | null | undefined; + + export async function spawn_detached(command: string, args?: readonly string[], out?: Sink): Promise { + if (!out) { + const logStream = createWriteStream(generate_log_name(command, args)); + out = await new Promise(resolve => logStream.on("open", resolve)); } + const child = spawn(command, args, { detached: true, stdio: ["ignore", out, out] }); + child.unref(); + return child; } let daemonInitialized = false; export async function trySpawnDaemon() { - if (!daemonInitialized) { + if (!process.env.SPAWNED && !daemonInitialized) { daemonInitialized = true; await log_execution({ startMessage: "\ninitializing persistence daemon", @@ -32,13 +45,15 @@ export namespace ProcessManager { const success = error === null && result !== undefined; if (!success) { console.log(red("failed to initialize the persistance daemon")); + console.log(error); process.exit(0); } - return "persistence daemon process closed"; + return "failsafe daemon process successfully spawned"; }, - action: () => spawn_detached_process("npx ts-node", [daemonPath]), - color: yellow + action: () => spawn_detached('npx', ['ts-node', daemonPath], process.stdout), + color: green }); + console.log(); } } diff --git a/src/server/daemon/current_daemon_pid.txt b/src/server/daemon/current_daemon_pid.txt new file mode 100644 index 000000000..f3cd0298c --- /dev/null +++ b/src/server/daemon/current_daemon_pid.txt @@ -0,0 +1 @@ +9626 \ No newline at end of file diff --git a/src/server/daemon/persistence_daemon.ts b/src/server/daemon/persistence_daemon.ts index 3eb17a9b4..099c7898c 100644 --- a/src/server/daemon/persistence_daemon.ts +++ b/src/server/daemon/persistence_daemon.ts @@ -1,22 +1,41 @@ import * as request from "request-promise"; -import { log_execution, spawn_detached_process } from "../ActionUtilities"; -import { red, yellow, cyan, green } from "colors"; +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 } from "fs"; +import { writeFileSync, appendFileSync, createWriteStream, existsSync } from "fs"; import { resolve } from 'path'; +import { ChildProcess } from "child_process"; +import { ProcessManager } from "../ProcessManager"; + +console.log(yellow("Initializing daemon...")); + +process.on('SIGINT', () => current_backup?.kill("SIGTERM")); + +const crashLogPath = resolve(__dirname, `./session_crashes_${timestamp()}.log`); +function addLogEntry(message: string, color: Color) { + const formatted = color(`${message} ${timestamp()}.`); + console.log(formatted); + appendFileSync(crashLogPath, `${formatted}\n`); +} const LOCATION = "http://localhost"; const recipient = "samuel_wilkins@brown.edu"; let restarting = false; -writeFileSync(resolve(__dirname, "./current_pid.txt"), process.pid); +const frequency = 10; +const { pid } = process; +writeFileSync(resolve(__dirname, "./current_daemon_pid.txt"), pid); +console.log(cyan(`${pid} written to ./current_daemon_pid.txt`)); function timestamp() { return `@ ${new Date().toISOString()}`; } +let current_backup: ChildProcess | undefined = undefined; + async function listen() { + console.log(yellow(`Beginning to poll server heartbeat every ${frequency} seconds...\n`)); if (!LOCATION) { console.log(red("No location specified for persistence daemon. Please include as a command line environment variable or in a .env file.")); process.exit(0); @@ -28,34 +47,40 @@ async function listen() { let error: any; try { await request.get(heartbeat); + if (restarting) { + addLogEntry("Backup server successfully restarted", green); + } + restarting = false; } catch (e) { error = e; } finally { if (error) { if (!restarting) { restarting = true; - console.log(yellow("Detected a server crash!")); + addLogEntry("Detected a server crash", red); + current_backup?.kill(); await log_execution({ startMessage: "Sending crash notification email", endMessage: ({ error, result }) => { const success = error === null && result === true; - return (success ? `Notification successfully sent to ` : `Failed to notify `) + recipient; + return `${(success ? `Notification successfully sent to` : `Failed to notify`)} ${recipient} ${timestamp()}`; }, action: async () => notify(error || "Hmm, no error to report..."), color: cyan }); - console.log(await log_execution({ + current_backup = await log_execution({ startMessage: "Initiating server restart", - endMessage: "Server successfully restarted", - action: () => spawn_detached_process(`npm run start-spawn`), + endMessage: ({ result, error }) => { + const success = error === null && result !== undefined; + return success ? "Child process spawned.." : `An error occurred while attempting to restart the server:\n${error}`; + }, + action: () => ProcessManager.spawn_detached('npm', ['run', 'start-spawn']), color: green - })); - restarting = false; + }); + writeFileSync(pathFromRoot("./logs/current_server_pid.txt"), `${current_backup?.pid ?? -1} created ${timestamp()}\n`); } else { console.log(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); } - } else { - console.log(green(`No issues detected ${timestamp()}`)); } } }, 1000 * 10); @@ -85,7 +110,7 @@ async function notify(error: any) { text: emailText(error) } as MailOptions; return new Promise(resolve => { - smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => { console.log(dispatchError); resolve(dispatchError === null); }); + smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); }); } diff --git a/src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log b/src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log new file mode 100644 index 000000000..32b7810ea --- /dev/null +++ b/src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log @@ -0,0 +1 @@ +Detected a server crash @ 2019-12-11T08:32:36.317Z diff --git a/src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log b/src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log new file mode 100644 index 000000000..ebb6843c2 --- /dev/null +++ b/src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log @@ -0,0 +1,2 @@ +Detected a server crash @ 2019-12-11T08:44:26.494Z. +Backup server successfully restarted @ 2019-12-11T08:45:33.644Z. -- cgit v1.2.3-70-g09d2 From ae3603e26adb635380d530b84cb9d6f1284066ef Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 11 Dec 2019 13:54:28 -0500 Subject: process factory refactor --- .gitignore | 1 + logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log | 274 --------------------- logs/server_pids.txt | 1 - src/server/ActionUtilities.ts | 9 +- src/server/ChildProcessUtilities/ProcessFactory.ts | 67 +++++ .../daemon/persistence_daemon.ts | 137 +++++++++++ src/server/ProcessManager.ts | 60 ----- src/server/daemon/current_daemon_pid.txt | 1 - src/server/daemon/persistence_daemon.ts | 117 --------- .../session_crashes_@ 2019-12-11T08:31:56.281Z.log | 1 - .../session_crashes_@ 2019-12-11T08:43:46.454Z.log | 2 - src/server/index.ts | 11 +- 12 files changed, 220 insertions(+), 461 deletions(-) delete mode 100644 logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log delete mode 100644 logs/server_pids.txt create mode 100644 src/server/ChildProcessUtilities/ProcessFactory.ts create mode 100644 src/server/ChildProcessUtilities/daemon/persistence_daemon.ts delete mode 100644 src/server/ProcessManager.ts delete mode 100644 src/server/daemon/current_daemon_pid.txt delete mode 100644 src/server/daemon/persistence_daemon.ts delete mode 100644 src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log delete mode 100644 src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log (limited to 'src/server') diff --git a/.gitignore b/.gitignore index cf4ed308b..38c619c52 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ ClientUtils.ts solr-8.1.1/server/ src/server/public/files/ src/scraping/acm/package-lock.json +src/server/ChildProcessUtilities/daemon/**/*.log diff --git a/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log b/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log deleted file mode 100644 index 37e232d48..000000000 --- a/logs/npm-2-Wed, 11 Dec 2019 08:44:28 GMT.log +++ /dev/null @@ -1,274 +0,0 @@ - -> dash@1.0.0 start-spawn /Users/swilkinss2012/Documents/GitHub/Dash-Web -> cross-env SPAWNED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts - -Using ts-node version 7.0.1, typescript version 3.7.2 -objc[9678]: Class GNotificationCenterDelegate is implemented in both /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/sharp/vendor/lib/libgio-2.0.0.dylib (0x10838d578) and /Users/swilkinss2012/Documents/GitHub/Dash-Web/node_modules/canvas/build/Release/libgio-2.0.0.dylib (0x10afd9578). One of the two will be used. Which one is undefined. - -starting execution of preliminary functions... -(node:9678) DeprecationWarning: current URL string parser is deprecated, and will be removed in a future version. To use the new parser, pass option { useNewUrlParser: true } to MongoClient.connect. -completed preliminary functions -. -Starting type checking and linting service... -Using 1 worker with 2048MB memory limit - -running server in development mode - -registering server routes... -all server routes have been successfully registered: -/ -/activity -/buxton -/delete -/deleteAll -/deleteWithAux -/deleteWithGoogleCredentials -/doc/:docId -/downloadId/:docId -/environment/:key -/getCurrentUser -/getUserDocumentId -/getUsers -/googleDocs/:sector/:action -/googlePhotosMediaDownload -/googlePhotosMediaUpload -/home -/imageHierarchyExport/:docId -/inspectImage -/persist -/pull -/readGoogleAccessToken -/search -/serializeDoc/:docId -/serverHeartbeat -/shutdown -/solr/:action -/textsearch -/thumbnail/:filename -/upload -/uploadDoc -/uploadURI -/version -/writeGoogleAccessToken - -websocket listening on port 4321 -server listening on port 1050 - -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -user samuel_wilkins@brown.edu has connected to the web socket -user samuel_wilkins@brown.edu has connected to the web socket -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -ℹ 「wdm」: wait until bundle finished: /serverHeartbeat -user samuel_wilkins@brown.edu has connected to the web socket -user samuel_wilkins@brown.edu has connected to the web socket -Type checking and linting in progress... -webpack built 8f6b743d91fd3862683b in 47419ms -⚠ 「wdm」: Hash: 8f6b743d91fd3862683b -Version: webpack 4.36.1 -Time: 47419ms -Built at: 12/11/2019 3:45:31 AM - Asset Size Chunks  Chunk Names -275711e56bd1bc79fdff544a3d7dbfae.png 289 bytes  [emitted] -32f1593298e6e7bee5673bf647328d72.png 429 bytes  [emitted] -718c914a99a2136c01c84e01f63e505a.png 829 bytes  [emitted] -906f1a1816c2a03b5c7612f6aa2ceece.png 281 bytes  [emitted] - assets/downarrow.png 3.28 KiB  [emitted] - assets/env.json 360 bytes  [emitted] - assets/google_photos.png 114 KiB  [emitted] - assets/google_tags.png 7.9 KiB  [emitted] - assets/loading.gif 112 KiB  [emitted] - assets/pdf.worker.js 1.55 MiB  [emitted] - bundle.js 20.8 MiB bundle [emitted] bundle - bundle.js.map 23.3 MiB bundle [emitted] bundle - debug/repl.html 348 bytes  [emitted] - debug/test.html 245 bytes  [emitted] - debug/viewer.html 357 bytes  [emitted] -e7a34b49f3c49ca0c25c76b30cd09e12.png 445 bytes  [emitted] - imageUpload.js 20.8 MiB imageUpload [emitted] imageUpload - imageUpload.js.map 23.3 MiB imageUpload [emitted] imageUpload - index.html 593 bytes  [emitted] - inkControls.js 116 KiB inkControls [emitted] inkControls - inkControls.js.map 122 KiB inkControls [emitted] inkControls - mobile/image.html 333 bytes  [emitted] - mobile/ink.html 252 bytes  [emitted] - repl.js 9.31 MiB repl [emitted] repl - repl.js.map 10.5 MiB repl [emitted] repl - test.js 1.2 MiB test [emitted] test - test.js.map 1.42 MiB test [emitted] test - test.pdf 53.6 KiB  [emitted] - vendors~pdfjsWorker.js 1.55 MiB vendors~pdfjsWorker [emitted] vendors~pdfjsWorker - vendors~pdfjsWorker.js.map 1.87 MiB vendors~pdfjsWorker [emitted] vendors~pdfjsWorker - viewer.js 9.47 MiB viewer [emitted] viewer - viewer.js.map 10.7 MiB viewer [emitted] viewer -Entrypoint bundle = bundle.js bundle.js.map -Entrypoint viewer = viewer.js viewer.js.map -Entrypoint repl = repl.js repl.js.map -Entrypoint test = test.js test.js.map -Entrypoint inkControls = inkControls.js inkControls.js.map -Entrypoint imageUpload = imageUpload.js imageUpload.js.map -[19] multi ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true 40 bytes {bundle} [built] -[20] multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true 40 bytes {viewer} [built] -[21] multi ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true 40 bytes {repl} [built] -[22] multi ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true 40 bytes {test} [built] -[23] multi ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true 40 bytes {inkControls} [built] -[24] multi ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true 40 bytes {imageUpload} [built] - [./node_modules/mobx-react/index.module.js] 48.8 KiB {bundle} {viewer} {repl} {imageUpload} [built] - [./node_modules/mobx/lib/mobx.module.js] 175 KiB {bundle} {viewer} {repl} {imageUpload} [built] - [./node_modules/webpack-hot-middleware/client.js?reload=true] (webpack)-hot-middleware/client.js?reload=true 7.68 KiB {bundle} {viewer} {repl} {test} {inkControls} {imageUpload} [built] - [./src/client/views/Main.tsx] 4.03 KiB {bundle} [built] - [./src/debug/Repl.tsx] 6.87 KiB {repl} [built] - [./src/debug/Test.tsx] 1.02 KiB {test} [built] - [./src/debug/Viewer.tsx] 12.1 KiB {viewer} [built] - [./src/mobile/ImageUpload.tsx] 9.97 KiB {imageUpload} [built] - [./src/mobile/InkControls.tsx] 14 bytes {inkControls} [built] - + 1494 hidden modules - -WARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60 -Critical dependency: the request of a dependency is an expression - @ ./src/client/util/Scripting.ts - @ ./src/debug/Viewer.tsx - @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true -ℹ 「wdm」: Compiled with warnings. -ℹ 「wdm」: Compiling... -webpack building... -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -ℹ 「wdm」: wait until bundle finished: /login -Type checking and linting in progress... -webpack built 8f6b743d91fd3862683b in 615ms -⚠ 「wdm」: Hash: 8f6b743d91fd3862683b -Version: webpack 4.36.1 -Time: 615ms -Built at: 12/11/2019 3:45:33 AM - Asset Size Chunks Chunk Names -275711e56bd1bc79fdff544a3d7dbfae.png 289 bytes   -32f1593298e6e7bee5673bf647328d72.png 429 bytes   -718c914a99a2136c01c84e01f63e505a.png 829 bytes   -906f1a1816c2a03b5c7612f6aa2ceece.png 281 bytes   - bundle.js 20.8 MiB bundle bundle - bundle.js.map 23.3 MiB bundle bundle -e7a34b49f3c49ca0c25c76b30cd09e12.png 445 bytes   - imageUpload.js 20.8 MiB imageUpload imageUpload - imageUpload.js.map 23.3 MiB imageUpload imageUpload - inkControls.js 116 KiB inkControls inkControls - inkControls.js.map 122 KiB inkControls inkControls - repl.js 9.31 MiB repl repl - repl.js.map 10.5 MiB repl repl - test.js 1.2 MiB test test - test.js.map 1.42 MiB test test - vendors~pdfjsWorker.js 1.55 MiB vendors~pdfjsWorker vendors~pdfjsWorker - vendors~pdfjsWorker.js.map 1.87 MiB vendors~pdfjsWorker vendors~pdfjsWorker - viewer.js 9.47 MiB viewer viewer - viewer.js.map 10.7 MiB viewer viewer -Entrypoint bundle = bundle.js bundle.js.map -Entrypoint viewer = viewer.js viewer.js.map -Entrypoint repl = repl.js repl.js.map -Entrypoint test = test.js test.js.map -Entrypoint inkControls = inkControls.js inkControls.js.map -Entrypoint imageUpload = imageUpload.js imageUpload.js.map -[19] multi ./src/client/views/Main.tsx webpack-hot-middleware/client?reload=true 40 bytes {bundle} -[20] multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true 40 bytes {viewer} -[21] multi ./src/debug/Repl.tsx webpack-hot-middleware/client?reload=true 40 bytes {repl} -[22] multi ./src/debug/Test.tsx webpack-hot-middleware/client?reload=true 40 bytes {test} -[23] multi ./src/mobile/InkControls.tsx webpack-hot-middleware/client?reload=true 40 bytes {inkControls} -[24] multi ./src/mobile/ImageUpload.tsx webpack-hot-middleware/client?reload=true 40 bytes {imageUpload} - [./node_modules/mobx-react/index.module.js] 48.8 KiB {bundle} {viewer} {repl} {imageUpload} - [./node_modules/mobx/lib/mobx.module.js] 175 KiB {bundle} {viewer} {repl} {imageUpload} - [./node_modules/webpack-hot-middleware/client.js?reload=true] (webpack)-hot-middleware/client.js?reload=true 7.68 KiB {bundle} {viewer} {repl} {test} {inkControls} {imageUpload} - [./src/client/views/Main.tsx] 4.03 KiB {bundle} - [./src/debug/Repl.tsx] 6.87 KiB {repl} - [./src/debug/Test.tsx] 1.02 KiB {test} - [./src/debug/Viewer.tsx] 12.1 KiB {viewer} - [./src/mobile/ImageUpload.tsx] 9.97 KiB {imageUpload} - [./src/mobile/InkControls.tsx] 14 bytes {inkControls} - + 1494 hidden modules - -WARNING in ./node_modules/typescript/lib/typescript.js 5121:41-60 -Critical dependency: the request of a dependency is an expression - @ ./src/client/util/Scripting.ts - @ ./src/debug/Viewer.tsx - @ multi ./src/debug/Viewer.tsx webpack-hot-middleware/client?reload=true -ℹ 「wdm」: Compiled with warnings. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(81,21): -prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(83,25): -prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(86,21): -prefer-const: Identifier 'isValidColor' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(87,25): -prefer-const: Identifier 's' is never reassigned; use 'const' instead of 'var'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(89,36): -triple-equals: == should be === -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(90,18): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(99,21): -prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(100,21): -prefer-const: Identifier 'marks' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(126,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(141,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(154,66): -no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(155,33): -no-unnecessary-type-assertion: This assertion is unnecessary since it does not change the type of the expression. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(160,25): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextRules.ts(192,29): -prefer-const: Identifier 'doc' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(640,134): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(649,21): -prefer-const: Identifier 'expand' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(650,21): -prefer-const: Identifier 'tr' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(654,138): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/util/RichTextSchema.tsx(658,10): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(243,13): -prefer-const: Identifier 'main' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(244,13): -prefer-const: Identifier 'next' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/collections/CollectionView.tsx(245,13): -prefer-const: Identifier 'prev' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(88,21): -prefer-const: Identifier 'selectionTitleFieldKey' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/DocumentDecorations.tsx(95,8): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/EditableView.tsx(123,13): -prefer-const: Identifier 'wasFocused' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/MainView.tsx(289,11): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/DocumentView.tsx(151,25): -prefer-const: Identifier 'any' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(910,13): -prefer-const: Identifier 'prosediv' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(911,13): -prefer-const: Identifier 'keeplocation' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(913,13): -prefer-const: Identifier 'pos' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(928,17): -prefer-const: Identifier 'pcords' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(929,17): -prefer-const: Identifier 'node' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(935,21): -prefer-const: Identifier 'lastNode' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(976,71): -semicolon: Missing semicolon -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(987,17): -prefer-const: Identifier '$pos' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1001,25): -prefer-const: Identifier '$olist_pos' is never reassigned; use 'const' instead of 'let'. -WARNING in /Users/swilkinss2012/Documents/GitHub/Dash-Web/src/client/views/nodes/FormattedTextBox.tsx(1103,17): -prefer-const: Identifier 'newHeight' is never reassigned; use 'const' instead of 'let'. -No type errors found -Version: typescript 3.7.2, tslint 5.18.0 -Time: 5473ms diff --git a/logs/server_pids.txt b/logs/server_pids.txt deleted file mode 100644 index 2aa143f24..000000000 --- a/logs/server_pids.txt +++ /dev/null @@ -1 +0,0 @@ -9675 created at Wed, 11 Dec 2019 08:44:28 GMT diff --git a/src/server/ActionUtilities.ts b/src/server/ActionUtilities.ts index 9bdc4ed93..2e62443c6 100644 --- a/src/server/ActionUtilities.ts +++ b/src/server/ActionUtilities.ts @@ -1,4 +1,4 @@ -import { readFile, writeFile, exists, mkdir, unlink } from 'fs'; +import { readFile, writeFile, exists, mkdir, unlink, createWriteStream } from 'fs'; import { ExecOptions } from 'shelljs'; import { exec } from 'child_process'; import * as path from 'path'; @@ -10,6 +10,11 @@ export function pathFromRoot(relative: string) { return path.resolve(projectRoot, relative); } +export async function fileDescriptorFromStream(path: string) { + const logStream = createWriteStream(path); + return new Promise(resolve => logStream.on("open", resolve)); +} + export const command_line = (command: string, fromDirectory?: string) => { return new Promise((resolve, reject) => { const options: ExecOptions = {}; @@ -54,7 +59,7 @@ export async function log_execution({ startMessage, endMessage, action, color } catch (e) { error = e; } finally { - log_helper(`${typeof endMessage === "string" ? endMessage : endMessage({ result, error })}.`, resolvedColor); + log_helper(typeof endMessage === "string" ? endMessage : endMessage({ result, error }), resolvedColor); } return result; } diff --git a/src/server/ChildProcessUtilities/ProcessFactory.ts b/src/server/ChildProcessUtilities/ProcessFactory.ts new file mode 100644 index 000000000..745b1479a --- /dev/null +++ b/src/server/ChildProcessUtilities/ProcessFactory.ts @@ -0,0 +1,67 @@ +import { existsSync, mkdirSync } from "fs"; +import { pathFromRoot, log_execution, fileDescriptorFromStream } from '../ActionUtilities'; +import { red, green } from "colors"; +import rimraf = require("rimraf"); +import { ChildProcess, spawn, StdioOptions } from "child_process"; +import { Stream } from "stream"; +import { resolve } from "path"; + +export namespace ProcessFactory { + + export type Sink = "pipe" | "ipc" | "ignore" | "inherit" | Stream | number | null | undefined; + + export async function createWorker(command: string, args?: readonly string[], stdio?: StdioOptions | "logfile", detached = true): Promise { + if (stdio === "logfile") { + const log_fd = await Logger.create(command, args); + stdio = ["ignore", log_fd, log_fd]; + } + const child = spawn(command, args, { detached, stdio }); + child.unref(); + return child; + } + + export namespace NamedAgents { + + export async function persistenceDaemon() { + await log_execution({ + startMessage: "\ninitializing persistence daemon", + endMessage: ({ result, error }) => { + const success = error === null && result !== undefined; + if (!success) { + console.log(red("failed to initialize the persistance daemon")); + console.log(error); + process.exit(0); + } + return "failsafe daemon process successfully spawned"; + }, + action: () => createWorker('npx', ['ts-node', resolve(__dirname, "./daemon/persistence_daemon.ts")], ["ignore", "inherit", "inherit"]), + color: green + }); + console.log(); + } + } + +} + +export namespace Logger { + + const logPath = pathFromRoot("./logs"); + + export async function initialize() { + if (existsSync(logPath)) { + if (!process.env.SPAWNED) { + await new Promise(resolve => rimraf(logPath, resolve)); + } + } + mkdirSync(logPath); + } + + export async function create(command: string, args?: readonly string[]): Promise { + return fileDescriptorFromStream(generate_log_path(command, args)); + } + + function generate_log_path(command: string, args?: readonly string[]) { + return pathFromRoot(`./logs/${command}-${args?.length}-${new Date().toUTCString()}.log`); + } + +} \ No newline at end of file diff --git a/src/server/ChildProcessUtilities/daemon/persistence_daemon.ts b/src/server/ChildProcessUtilities/daemon/persistence_daemon.ts new file mode 100644 index 000000000..888cf38b8 --- /dev/null +++ b/src/server/ChildProcessUtilities/daemon/persistence_daemon.ts @@ -0,0 +1,137 @@ +import * as request from "request-promise"; +import { log_execution } 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 { resolve } from 'path'; +import { ChildProcess } from "child_process"; +import { ProcessFactory } from "../ProcessFactory"; + +const identifier = yellow("__daemon__:"); + +process.on('SIGINT', () => current_backup?.kill("SIGTERM")); + +const logPath = resolve(__dirname, "./logs"); +const crashPath = resolve(logPath, "./crashes"); +if (!existsSync(logPath)) { + mkdirSync(logPath); +} +if (!existsSync(crashPath)) { + mkdirSync(crashPath); +} + +const crashLogPath = resolve(crashPath, `./session_crashes_${timestamp()}.log`); +function addLogEntry(message: string, color: Color) { + const formatted = color(`${message} ${timestamp()}.`); + identifiedLog(formatted); + appendFileSync(crashLogPath, `${formatted}\n`); +} + +function identifiedLog(message?: any, ...optionalParams: any[]) { + console.log(identifier, message, ...optionalParams); +} + +const LOCATION = "http://localhost"; +const recipient = "samuel_wilkins@brown.edu"; +const frequency = 10; +const { pid } = process; +let restarting = false; + +identifiedLog("Initializing daemon..."); + +writeLocalPidLog("daemon", pid); + +function writeLocalPidLog(filename: string, contents: any) { + const path = `./logs/current_${filename}_pid.log`; + identifiedLog(cyan(`${contents} written to ${path}`)); + writeFileSync(resolve(__dirname, path), `${contents}\n`); +} + +function timestamp() { + return `@ ${new Date().toISOString()}`; +} + +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 restarted", green); + } + restarting = false; + } catch (e) { + error = e; + } finally { + if (error) { + if (!restarting) { + restarting = true; + addLogEntry("Detected a server crash", red); + current_backup?.kill(); + 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 + }); + current_backup = await log_execution({ + startMessage: identifier + " Initiating server restart", + endMessage: ({ result, error }) => { + const success = error === null && result !== undefined; + return identifier + success ? " Child process spawned..." : ` An error occurred while attempting to restart the server:\n${error}`; + }, + action: () => ProcessFactory.createWorker('npm', ['run', 'start-spawn'], "inherit"), + color: green + }); + writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); + } else { + identifiedLog(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); + } + } + } + }, 1000 * 10); +} + +function emailText(error: any) { + return [ + `Hey ${recipient.split("@")[0]},`, + "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", + `Location: ${LOCATION}\nError: ${error}`, + "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress." + ].join("\n\n"); +} + +async function notify(error: any) { + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'brownptcdash@gmail.com', + pass: 'browngfx1' + } + }); + const mailOptions = { + to: recipient, + from: 'brownptcdash@gmail.com', + subject: 'Dash Server Crash', + text: emailText(error) + } as MailOptions; + return new Promise(resolve => { + smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); + }); +} + +listen(); \ No newline at end of file diff --git a/src/server/ProcessManager.ts b/src/server/ProcessManager.ts deleted file mode 100644 index 671f0a234..000000000 --- a/src/server/ProcessManager.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { existsSync, mkdirSync, createWriteStream } from "fs"; -import { pathFromRoot, log_execution } from './ActionUtilities'; -import { red, green } from "colors"; -import rimraf = require("rimraf"); -import { ChildProcess, spawn } from "child_process"; -import { Stream } from "stream"; - -const daemonPath = pathFromRoot("./src/server/daemon/persistence_daemon.ts"); - -export namespace ProcessManager { - - export async function initialize() { - const logPath = pathFromRoot("./logs"); - if (existsSync(logPath)) { - if (!process.env.SPAWNED) { - await new Promise(resolve => rimraf(logPath, resolve)); - } - } - mkdirSync(logPath); - } - - function generate_log_name(command: string, args?: readonly string[]) { - return pathFromRoot(`./logs/${command}-${args?.length}-${new Date().toUTCString()}.log`); - } - - export type Sink = "pipe" | "ipc" | "ignore" | "inherit" | Stream | number | null | undefined; - - export async function spawn_detached(command: string, args?: readonly string[], out?: Sink): Promise { - if (!out) { - const logStream = createWriteStream(generate_log_name(command, args)); - out = await new Promise(resolve => logStream.on("open", resolve)); - } - const child = spawn(command, args, { detached: true, stdio: ["ignore", out, out] }); - child.unref(); - return child; - } - - let daemonInitialized = false; - export async function trySpawnDaemon() { - if (!process.env.SPAWNED && !daemonInitialized) { - daemonInitialized = true; - await log_execution({ - startMessage: "\ninitializing persistence daemon", - endMessage: ({ result, error }) => { - const success = error === null && result !== undefined; - if (!success) { - console.log(red("failed to initialize the persistance daemon")); - console.log(error); - process.exit(0); - } - return "failsafe daemon process successfully spawned"; - }, - action: () => spawn_detached('npx', ['ts-node', daemonPath], process.stdout), - color: green - }); - console.log(); - } - } - -} \ No newline at end of file diff --git a/src/server/daemon/current_daemon_pid.txt b/src/server/daemon/current_daemon_pid.txt deleted file mode 100644 index f3cd0298c..000000000 --- a/src/server/daemon/current_daemon_pid.txt +++ /dev/null @@ -1 +0,0 @@ -9626 \ No newline at end of file diff --git a/src/server/daemon/persistence_daemon.ts b/src/server/daemon/persistence_daemon.ts deleted file mode 100644 index 099c7898c..000000000 --- a/src/server/daemon/persistence_daemon.ts +++ /dev/null @@ -1,117 +0,0 @@ -import * as request from "request-promise"; -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, createWriteStream, existsSync } from "fs"; -import { resolve } from 'path'; -import { ChildProcess } from "child_process"; -import { ProcessManager } from "../ProcessManager"; - -console.log(yellow("Initializing daemon...")); - -process.on('SIGINT', () => current_backup?.kill("SIGTERM")); - -const crashLogPath = resolve(__dirname, `./session_crashes_${timestamp()}.log`); -function addLogEntry(message: string, color: Color) { - const formatted = color(`${message} ${timestamp()}.`); - console.log(formatted); - appendFileSync(crashLogPath, `${formatted}\n`); -} - -const LOCATION = "http://localhost"; -const recipient = "samuel_wilkins@brown.edu"; -let restarting = false; - -const frequency = 10; -const { pid } = process; -writeFileSync(resolve(__dirname, "./current_daemon_pid.txt"), pid); -console.log(cyan(`${pid} written to ./current_daemon_pid.txt`)); - -function timestamp() { - return `@ ${new Date().toISOString()}`; -} - -let current_backup: ChildProcess | undefined = undefined; - -async function listen() { - console.log(yellow(`Beginning to poll server heartbeat every ${frequency} seconds...\n`)); - if (!LOCATION) { - console.log(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 restarted", green); - } - restarting = false; - } catch (e) { - error = e; - } finally { - if (error) { - if (!restarting) { - restarting = true; - addLogEntry("Detected a server crash", red); - current_backup?.kill(); - await log_execution({ - startMessage: "Sending crash notification email", - endMessage: ({ error, result }) => { - const success = error === null && result === true; - return `${(success ? `Notification successfully sent to` : `Failed to notify`)} ${recipient} ${timestamp()}`; - }, - action: async () => notify(error || "Hmm, no error to report..."), - color: cyan - }); - current_backup = await log_execution({ - startMessage: "Initiating server restart", - endMessage: ({ result, error }) => { - const success = error === null && result !== undefined; - return success ? "Child process spawned.." : `An error occurred while attempting to restart the server:\n${error}`; - }, - action: () => ProcessManager.spawn_detached('npm', ['run', 'start-spawn']), - color: green - }); - writeFileSync(pathFromRoot("./logs/current_server_pid.txt"), `${current_backup?.pid ?? -1} created ${timestamp()}\n`); - } else { - console.log(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); - } - } - } - }, 1000 * 10); -} - -function emailText(error: any) { - return [ - `Hey ${recipient.split("@")[0]},`, - "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", - `Location: ${LOCATION}\nError: ${error}`, - "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress." - ].join("\n\n"); -} - -async function notify(error: any) { - const smtpTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'brownptcdash@gmail.com', - pass: 'browngfx1' - } - }); - const mailOptions = { - to: recipient, - from: 'brownptcdash@gmail.com', - subject: 'Dash Server Crash', - text: emailText(error) - } as MailOptions; - return new Promise(resolve => { - smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); - }); -} - -listen(); \ No newline at end of file diff --git a/src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log b/src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log deleted file mode 100644 index 32b7810ea..000000000 --- a/src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log +++ /dev/null @@ -1 +0,0 @@ -Detected a server crash @ 2019-12-11T08:32:36.317Z diff --git a/src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log b/src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log deleted file mode 100644 index ebb6843c2..000000000 --- a/src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log +++ /dev/null @@ -1,2 +0,0 @@ -Detected a server crash @ 2019-12-11T08:44:26.494Z. -Backup server successfully restarted @ 2019-12-11T08:45:33.644Z. diff --git a/src/server/index.ts b/src/server/index.ts index 795418b31..bebb9b365 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -23,7 +23,7 @@ import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager"; import GooglePhotosManager from "./ApiManagers/GooglePhotosManager"; import { yellow, red } from "colors"; import { disconnect } from "../server/Initialization"; -import { ProcessManager } from "./ProcessManager"; +import { ProcessFactory, Logger } from "./ChildProcessUtilities/ProcessFactory"; export const publicDirectory = path.resolve(__dirname, "public"); export const filesDirectory = path.resolve(publicDirectory, "files"); @@ -36,7 +36,7 @@ export const ExitHandlers = new Array<() => void>(); * before clients can access the server should be run or awaited here. */ async function preliminaryFunctions() { - await ProcessManager.initialize(); + await Logger.initialize(); await GoogleCredentialsLoader.loadCredentials(); GoogleApiServerUtils.processProjectCredentials(); await DashUploadUtils.buildFileDirectories(); @@ -121,11 +121,16 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: } }); + let daemonInitialized = false; + const { SPAWNED, RELEASE } = process.env; addSupervisedRoute({ method: Method.GET, subscription: "/persist", onValidation: ({ res }) => { - ProcessManager.trySpawnDaemon(); + if (RELEASE && !SPAWNED && !daemonInitialized) { + daemonInitialized = true; + ProcessFactory.NamedAgents.persistenceDaemon(); + } res.redirect("/home"); } }); -- cgit v1.2.3-70-g09d2 From 4e8605c9e46acd3f3d9080073bdd6e80f049c85a Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 11 Dec 2019 18:45:05 -0500 Subject: fixed searching in text boxes --- src/server/Websocket/Websocket.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/server') diff --git a/src/server/Websocket/Websocket.ts b/src/server/Websocket/Websocket.ts index 76e02122b..e1e157fc4 100644 --- a/src/server/Websocket/Websocket.ts +++ b/src/server/Websocket/Websocket.ts @@ -133,6 +133,7 @@ export namespace WebSocket { "pdf": ["_t", "url"], "audio": ["_t", "url"], "web": ["_t", "url"], + "RichTextField": ["_t", value => value.Text], "date": ["_d", value => new Date(value.date).toISOString()], "proxy": ["_i", "fieldId"], "list": ["_l", list => { -- cgit v1.2.3-70-g09d2 From e01499153fdd335493973dc79d80ccc4ae8df95c Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Wed, 11 Dec 2019 19:43:37 -0500 Subject: first steps toward restructure of session daemon --- package.json | 2 +- .../daemon/persistence_daemon.ts | 137 --------------------- src/server/ChildProcessUtilities/daemon/session.ts | 137 +++++++++++++++++++++ src/server/index.ts | 14 --- 4 files changed, 138 insertions(+), 152 deletions(-) delete mode 100644 src/server/ChildProcessUtilities/daemon/persistence_daemon.ts create mode 100644 src/server/ChildProcessUtilities/daemon/session.ts (limited to 'src/server') diff --git a/package.json b/package.json index 97563e137..77a521e27 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "start-release": "cross-env RELEASE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", - "start-spawn": "cross-env SPAWNED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", + "session": "npx ts-node ./src/server/ChildProcessUtilities/daemon/session.ts", "start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts", "debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --inspect -- src/server/index.ts", "build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production", diff --git a/src/server/ChildProcessUtilities/daemon/persistence_daemon.ts b/src/server/ChildProcessUtilities/daemon/persistence_daemon.ts deleted file mode 100644 index 888cf38b8..000000000 --- a/src/server/ChildProcessUtilities/daemon/persistence_daemon.ts +++ /dev/null @@ -1,137 +0,0 @@ -import * as request from "request-promise"; -import { log_execution } 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 { resolve } from 'path'; -import { ChildProcess } from "child_process"; -import { ProcessFactory } from "../ProcessFactory"; - -const identifier = yellow("__daemon__:"); - -process.on('SIGINT', () => current_backup?.kill("SIGTERM")); - -const logPath = resolve(__dirname, "./logs"); -const crashPath = resolve(logPath, "./crashes"); -if (!existsSync(logPath)) { - mkdirSync(logPath); -} -if (!existsSync(crashPath)) { - mkdirSync(crashPath); -} - -const crashLogPath = resolve(crashPath, `./session_crashes_${timestamp()}.log`); -function addLogEntry(message: string, color: Color) { - const formatted = color(`${message} ${timestamp()}.`); - identifiedLog(formatted); - appendFileSync(crashLogPath, `${formatted}\n`); -} - -function identifiedLog(message?: any, ...optionalParams: any[]) { - console.log(identifier, message, ...optionalParams); -} - -const LOCATION = "http://localhost"; -const recipient = "samuel_wilkins@brown.edu"; -const frequency = 10; -const { pid } = process; -let restarting = false; - -identifiedLog("Initializing daemon..."); - -writeLocalPidLog("daemon", pid); - -function writeLocalPidLog(filename: string, contents: any) { - const path = `./logs/current_${filename}_pid.log`; - identifiedLog(cyan(`${contents} written to ${path}`)); - writeFileSync(resolve(__dirname, path), `${contents}\n`); -} - -function timestamp() { - return `@ ${new Date().toISOString()}`; -} - -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 restarted", green); - } - restarting = false; - } catch (e) { - error = e; - } finally { - if (error) { - if (!restarting) { - restarting = true; - addLogEntry("Detected a server crash", red); - current_backup?.kill(); - 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 - }); - current_backup = await log_execution({ - startMessage: identifier + " Initiating server restart", - endMessage: ({ result, error }) => { - const success = error === null && result !== undefined; - return identifier + success ? " Child process spawned..." : ` An error occurred while attempting to restart the server:\n${error}`; - }, - action: () => ProcessFactory.createWorker('npm', ['run', 'start-spawn'], "inherit"), - color: green - }); - writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); - } else { - identifiedLog(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); - } - } - } - }, 1000 * 10); -} - -function emailText(error: any) { - return [ - `Hey ${recipient.split("@")[0]},`, - "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", - `Location: ${LOCATION}\nError: ${error}`, - "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress." - ].join("\n\n"); -} - -async function notify(error: any) { - const smtpTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'brownptcdash@gmail.com', - pass: 'browngfx1' - } - }); - const mailOptions = { - to: recipient, - from: 'brownptcdash@gmail.com', - subject: 'Dash Server Crash', - text: emailText(error) - } as MailOptions; - return new Promise(resolve => { - smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); - }); -} - -listen(); \ No newline at end of file diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts new file mode 100644 index 000000000..c41657cc9 --- /dev/null +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -0,0 +1,137 @@ +import * as request from "request-promise"; +import { log_execution } 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 { resolve } from 'path'; +import { ChildProcess } from "child_process"; +import { ProcessFactory } from "../ProcessFactory"; + +const identifier = yellow("__daemon__:"); + +process.on('SIGINT', () => current_backup?.kill("SIGTERM")); + +const logPath = resolve(__dirname, "./logs"); +const crashPath = resolve(logPath, "./crashes"); +if (!existsSync(logPath)) { + mkdirSync(logPath); +} +if (!existsSync(crashPath)) { + mkdirSync(crashPath); +} + +const crashLogPath = resolve(crashPath, `./session_crashes_${timestamp()}.log`); +function addLogEntry(message: string, color: Color) { + const formatted = color(`${message} ${timestamp()}.`); + identifiedLog(formatted); + appendFileSync(crashLogPath, `${formatted}\n`); +} + +function identifiedLog(message?: any, ...optionalParams: any[]) { + console.log(identifier, message, ...optionalParams); +} + +const LOCATION = "http://localhost"; +const recipient = "samuel_wilkins@brown.edu"; +const frequency = 10; +const { pid } = process; +let restarting = false; + +identifiedLog("Initializing daemon..."); + +writeLocalPidLog("daemon", pid); + +function writeLocalPidLog(filename: string, contents: any) { + const path = `./logs/current_${filename}_pid.log`; + identifiedLog(cyan(`${contents} written to ${path}`)); + writeFileSync(resolve(__dirname, path), `${contents}\n`); +} + +function timestamp() { + return `@ ${new Date().toISOString()}`; +} + +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 restarted", green); + } + restarting = false; + } catch (e) { + error = e; + } finally { + if (error) { + if (!restarting) { + restarting = true; + addLogEntry("Detected a server crash", red); + current_backup?.kill(); + 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 + }); + current_backup = await log_execution({ + startMessage: identifier + " Initiating server restart", + endMessage: ({ result, error }) => { + const success = error === null && result !== undefined; + return identifier + success ? " Child process spawned..." : ` An error occurred while attempting to restart the server:\n${error}`; + }, + action: () => ProcessFactory.createWorker('npm', ['run', 'start'], "inherit"), + color: green + }); + writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); + } else { + identifiedLog(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); + } + } + } + }, 1000 * 10); +} + +function emailText(error: any) { + return [ + `Hey ${recipient.split("@")[0]},`, + "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", + `Location: ${LOCATION}\nError: ${error}`, + "The server should already be restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress." + ].join("\n\n"); +} + +async function notify(error: any) { + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'brownptcdash@gmail.com', + pass: 'browngfx1' + } + }); + const mailOptions = { + to: recipient, + from: 'brownptcdash@gmail.com', + subject: 'Dash Server Crash', + text: emailText(error) + } as MailOptions; + return new Promise(resolve => { + smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); + }); +} + +listen(); \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index bebb9b365..bc481e579 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -121,20 +121,6 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: } }); - let daemonInitialized = false; - const { SPAWNED, RELEASE } = process.env; - addSupervisedRoute({ - method: Method.GET, - subscription: "/persist", - onValidation: ({ res }) => { - if (RELEASE && !SPAWNED && !daemonInitialized) { - daemonInitialized = true; - ProcessFactory.NamedAgents.persistenceDaemon(); - } - res.redirect("/home"); - } - }); - logRegistrationOutcome(); // initialize the web socket (bidirectional communication: if a user changes -- cgit v1.2.3-70-g09d2 From b55ce381c905e87421bf011f5cd0cf423f858bb8 Mon Sep 17 00:00:00 2001 From: Sam Wilkins <35748010+samwilkins333@users.noreply.github.com> Date: Thu, 12 Dec 2019 10:51:47 -0500 Subject: session --- src/server/ChildProcessUtilities/daemon/session.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'src/server') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index c41657cc9..9e082e665 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -5,7 +5,7 @@ import * as nodemailer from "nodemailer"; import { MailOptions } from "nodemailer/lib/json-transport"; import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs"; import { resolve } from 'path'; -import { ChildProcess } from "child_process"; +import { ChildProcess, exec } from "child_process"; import { ProcessFactory } from "../ProcessFactory"; const identifier = yellow("__daemon__:"); @@ -21,11 +21,11 @@ if (!existsSync(crashPath)) { mkdirSync(crashPath); } -const crashLogPath = resolve(crashPath, `./session_crashes_${timestamp()}.log`); +const crashLogPath = resolve(crashPath, `./session_crashes_${new Date().toISOString()}.log`); function addLogEntry(message: string, color: Color) { const formatted = color(`${message} ${timestamp()}.`); identifiedLog(formatted); - appendFileSync(crashLogPath, `${formatted}\n`); + // appendFileSync(crashLogPath, `${formatted}\n`); } function identifiedLog(message?: any, ...optionalParams: any[]) { @@ -88,13 +88,24 @@ async function listen() { action: async () => notify(error || "Hmm, no error to report..."), color: cyan }); - current_backup = await log_execution({ + await log_execution({ startMessage: identifier + " Initiating server restart", endMessage: ({ result, error }) => { const success = error === null && result !== undefined; return identifier + success ? " Child process spawned..." : ` An error occurred while attempting to restart the server:\n${error}`; }, - action: () => ProcessFactory.createWorker('npm', ['run', 'start'], "inherit"), + action: async () => { + return new Promise(resolve => { + exec('"C:\\Program Files\\Git\\git-bash.exe" -c "npm run start-release"', err => { + if (err) { + identifiedLog(err.message); + return; + } + resolve(); + }); + + }); + }, color: green }); writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); -- cgit v1.2.3-70-g09d2 From 85e7ca251beefa0b5417c8e6e6d28f9aaa5d886c Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 11:05:36 -0500 Subject: killport --- package.json | 3 ++- src/server/ChildProcessUtilities/daemon/session.ts | 25 ++++------------------ src/typings/index.d.ts | 1 + 3 files changed, 7 insertions(+), 22 deletions(-) (limited to 'src/server') diff --git a/package.json b/package.json index 77a521e27..c58f542fc 100644 --- a/package.json +++ b/package.json @@ -161,6 +161,7 @@ "js-datepicker": "^4.6.6", "jsonwebtoken": "^8.5.0", "jsx-to-string": "^1.4.0", + "kill-port": "^1.6.0", "lodash": "^4.17.15", "mobile-detect": "^1.4.3", "mobx": "^5.15.0", @@ -231,4 +232,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} \ No newline at end of file +} diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 9e082e665..41eb6976e 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -6,7 +6,7 @@ import { MailOptions } from "nodemailer/lib/json-transport"; import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs"; import { resolve } from 'path'; import { ChildProcess, exec } from "child_process"; -import { ProcessFactory } from "../ProcessFactory"; +import * as killport from "kill-port"; const identifier = yellow("__daemon__:"); @@ -79,6 +79,7 @@ async function listen() { restarting = true; addLogEntry("Detected a server crash", red); current_backup?.kill(); + await killport(1050, 'tcp'); await log_execution({ startMessage: identifier + " Sending crash notification email", endMessage: ({ error, result }) => { @@ -88,26 +89,8 @@ async function listen() { action: async () => notify(error || "Hmm, no error to report..."), color: cyan }); - await log_execution({ - startMessage: identifier + " Initiating server restart", - endMessage: ({ result, error }) => { - const success = error === null && result !== undefined; - return identifier + success ? " Child process spawned..." : ` An error occurred while attempting to restart the server:\n${error}`; - }, - action: async () => { - return new Promise(resolve => { - exec('"C:\\Program Files\\Git\\git-bash.exe" -c "npm run start-release"', err => { - if (err) { - identifiedLog(err.message); - return; - } - resolve(); - }); - - }); - }, - color: green - }); + 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()}`); } else { identifiedLog(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 9e8ace962..674278126 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -2,6 +2,7 @@ declare module 'googlephotos'; declare module 'react-image-lightbox-with-rotate'; +declare module 'kill-port'; declare module '@react-pdf/renderer' { import * as React from 'react'; -- cgit v1.2.3-70-g09d2 From 2a3a9c641da29e92ddd321f6c1f6c62ffe3c040d Mon Sep 17 00:00:00 2001 From: Sam Wilkins <35748010+samwilkins333@users.noreply.github.com> Date: Thu, 12 Dec 2019 11:40:56 -0500 Subject: functional session --- src/server/ChildProcessUtilities/daemon/session.ts | 39 ++++++++++++---------- 1 file changed, 22 insertions(+), 17 deletions(-) (limited to 'src/server') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 41eb6976e..d6c8ddb64 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -6,7 +6,7 @@ import { MailOptions } from "nodemailer/lib/json-transport"; import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs"; import { resolve } from 'path'; import { ChildProcess, exec } from "child_process"; -import * as killport from "kill-port"; +const killport = require("kill-port"); const identifier = yellow("__daemon__:"); @@ -37,6 +37,7 @@ const recipient = "samuel_wilkins@brown.edu"; const frequency = 10; const { pid } = process; let restarting = false; +let count = 0; identifiedLog("Initializing daemon..."); @@ -68,7 +69,8 @@ async function listen() { try { await request.get(heartbeat); if (restarting) { - addLogEntry("Backup server successfully restarted", green); + addLogEntry("Backup server successfully " + count ? "restarted" : "started", green); + count++; } restarting = false; } catch (e) { @@ -77,23 +79,26 @@ async function listen() { if (error) { if (!restarting) { restarting = true; - addLogEntry("Detected a server crash", red); - current_backup?.kill(); - await killport(1050, 'tcp'); - 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...")); + 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()}`); - } else { - identifiedLog(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); } } } -- cgit v1.2.3-70-g09d2 From d8cac4f0c1d425ef3fd263ae8d488ee62232f1d3 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 15:00:42 -0500 Subject: platform dependent --- src/server/ActionUtilities.ts | 5 ++++- src/server/ChildProcessUtilities/daemon/session.ts | 23 +++++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) (limited to 'src/server') 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 41eb6976e..1a3ce61af 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -1,12 +1,12 @@ 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"; -import * as killport from "kill-port"; +// import * as killport from "kill-port"; const identifier = yellow("__daemon__:"); @@ -32,12 +32,25 @@ 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 recipient = "samuel_wilkins@brown.edu"; const frequency = 10; const { pid } = process; let restarting = false; +function startCommand() { + 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 daemon..."); writeLocalPidLog("daemon", pid); @@ -79,7 +92,7 @@ async function listen() { restarting = true; addLogEntry("Detected a server crash", red); current_backup?.kill(); - await killport(1050, 'tcp'); + // await killport(1050, 'tcp'); await log_execution({ startMessage: identifier + " Sending crash notification email", endMessage: ({ error, result }) => { @@ -90,7 +103,7 @@ async function listen() { 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.")); + current_backup = exec(startCommand(), err => identifiedLog(err?.message || "Previous server process exited.")); writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); } else { identifiedLog(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); -- cgit v1.2.3-70-g09d2 From 1050212f66f9a906e9f2847e6d6171f488ba2179 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 16:09:20 -0500 Subject: small changes --- src/server/ChildProcessUtilities/daemon/session.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/server') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 833e90581..d51240349 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -81,8 +81,7 @@ async function checkHeartbeat() { try { await request.get(heartbeat); if (restarting) { - addLogEntry(`Backup server successfully ${count ? "restarted" : "started"}`, green); - count++; + addLogEntry(count++ ? "Backup server successfully restarted" : "Server successfully started", green); } restarting = false; } catch (e) { @@ -98,7 +97,8 @@ async function checkHeartbeat() { 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.")); + identifiedLog(yellow("Finished attempting to clear all ports.")); + identifiedLog(yellow("Any failures will be printed in red immediately above.")); await log_execution({ startMessage: identifier + " Sending crash notification email", -- cgit v1.2.3-70-g09d2 From 551eccb37f9dad21886a85f0e91571aa588ea505 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 17:41:53 -0500 Subject: read write --- src/server/ChildProcessUtilities/daemon/session.ts | 51 +++++++++++++++------- 1 file changed, 35 insertions(+), 16 deletions(-) (limited to 'src/server') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index d51240349..3ae14e894 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -5,12 +5,33 @@ import * as nodemailer from "nodemailer"; import { MailOptions } from "nodemailer/lib/json-transport"; import { writeFileSync, existsSync, mkdirSync } from "fs"; import { resolve } from 'path'; -import { ChildProcess, exec } from "child_process"; +import { ChildProcess, exec, execSync } from "child_process"; +import { createInterface } from "readline"; const killport = require("kill-port"); +process.on('SIGINT', endPrevious); const identifier = yellow("__session_manager__:"); -process.on('SIGINT', () => current_backup?.kill("SIGTERM")); +let manualRestartActive = false; +createInterface(process.stdin, process.stdout).on('line', async line => { + const prompt = line.trim().toLowerCase(); + switch (prompt) { + case "restart": + manualRestartActive = true; + identifiedLog(cyan("Initializing manual restart...")); + endPrevious(); + break; + case "exit": + identifiedLog(cyan("Initializing session end")); + await endPrevious(); + identifiedLog("Cleanup complete. Exiting session..."); + execSync("killall -9 node"); + break; + default: + identifiedLog(red("commands: { exit, restart }")); + return; + } +}); const logPath = resolve(__dirname, "./logs"); const crashPath = resolve(logPath, "./crashes"); @@ -37,6 +58,7 @@ if (!["win32", "darwin"].includes(process.platform)) { process.exit(1); } +const ports = [1050, 4321]; const onWindows = process.platform === "win32"; const LOCATION = "http://localhost"; const heartbeat = `${LOCATION}:1050/serverHeartbeat`; @@ -67,11 +89,14 @@ function timestamp() { return `@ ${new Date().toISOString()}`; } -async function clear_ports(...targets: number[]) { - return Promise.all(targets.map(port => { +async function endPrevious() { + identifiedLog(yellow("Cleaning up previous connections...")); + current_backup?.kill("SIGTERM"); + await Promise.all(ports.map(port => { const task = killport(port, 'tcp'); return task.catch((error: any) => identifiedLog(red(error))); })); + identifiedLog(yellow("Done. Any failures will be printed in red immediately above.")); } let current_backup: ChildProcess | undefined = undefined; @@ -80,26 +105,20 @@ async function checkHeartbeat() { let error: any; try { await request.get(heartbeat); - if (restarting) { + if (restarting || manualRestartActive) { addLogEntry(count++ ? "Backup server successfully restarted" : "Server successfully started", green); + restarting = false; } - restarting = false; } catch (e) { error = e; } finally { if (error) { - if (!restarting) { + if (!restarting || manualRestartActive) { restarting = true; - if (count) { + if (count && !manualRestartActive) { 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.")); - identifiedLog(yellow("Any failures will be printed in red immediately above.")); - + await endPrevious(); await log_execution({ startMessage: identifier + " Sending crash notification email", endMessage: ({ error, result }) => { @@ -109,9 +128,9 @@ async function checkHeartbeat() { action: async () => notify(error || "Hmm, no error to report..."), color: cyan }); - identifiedLog(green("Initiating server restart...")); } + manualRestartActive = false; 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()}`); } -- cgit v1.2.3-70-g09d2 From 5aaaf39260cf998008403fdb6f408f7c27469edc Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 17:44:59 -0500 Subject: kill all for windows --- src/server/ChildProcessUtilities/daemon/session.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src/server') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 3ae14e894..4a62381f8 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -24,8 +24,8 @@ createInterface(process.stdin, process.stdout).on('line', async line => { case "exit": identifiedLog(cyan("Initializing session end")); await endPrevious(); - identifiedLog("Cleanup complete. Exiting session..."); - execSync("killall -9 node"); + identifiedLog("Cleanup complete. Exiting session...\n"); + execSync(killAllCommand()); break; default: identifiedLog(red("commands: { exit, restart }")); @@ -75,6 +75,13 @@ function startServerCommand() { return `osascript -e 'tell app "Terminal"\ndo script "cd ${pathFromRoot()} && npm run start-release"\nend tell'`; } +function killAllCommand() { + if (onWindows) { + return "taskkill /f /im node.exe"; + } + return "killall -9 node"; +} + identifiedLog("Initializing session..."); writeLocalPidLog("session_manager", pid); -- cgit v1.2.3-70-g09d2 From 634475c34cdc49672edebb722e469ed627d495c2 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 12 Dec 2019 20:08:05 -0500 Subject: slight changes to session heartbeat --- src/server/ChildProcessUtilities/daemon/session.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/server') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 4a62381f8..f3d9b42c3 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -111,12 +111,15 @@ let current_backup: ChildProcess | undefined = undefined; async function checkHeartbeat() { let error: any; try { + identifiedLog(green("request Heartbeat...")); await request.get(heartbeat); + identifiedLog(green("got Heartbeat...")); if (restarting || manualRestartActive) { addLogEntry(count++ ? "Backup server successfully restarted" : "Server successfully started", green); restarting = false; } } catch (e) { + identifiedLog(red("failed Heartbeat..." + e)); error = e; } finally { if (error) { @@ -142,6 +145,8 @@ async function checkHeartbeat() { writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); } } + identifiedLog(green("restarting heartbeater")); + setTimeout(checkHeartbeat, 1000 * frequency); } } @@ -152,7 +157,6 @@ async function startListening() { process.exit(0); } await checkHeartbeat(); - setInterval(checkHeartbeat, 1000 * frequency); } function emailText(error: any) { -- cgit v1.2.3-70-g09d2 From 27a124671155f5178691707a4fff73aa071cd741 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 20:44:30 -0500 Subject: small session changes --- src/server/ChildProcessUtilities/daemon/session.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'src/server') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index f3d9b42c3..35c23b525 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -58,12 +58,12 @@ if (!["win32", "darwin"].includes(process.platform)) { process.exit(1); } +const latency = 10; const ports = [1050, 4321]; 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; @@ -111,15 +111,18 @@ let current_backup: ChildProcess | undefined = undefined; async function checkHeartbeat() { let error: any; try { - identifiedLog(green("request Heartbeat...")); + count && identifiedLog(green("Requesting heartbeat")); await request.get(heartbeat); - identifiedLog(green("got Heartbeat...")); + count && identifiedLog(green("Received heartbeat")); if (restarting || manualRestartActive) { addLogEntry(count++ ? "Backup server successfully restarted" : "Server successfully started", green); restarting = false; } } catch (e) { - identifiedLog(red("failed Heartbeat..." + e)); + if (count) { + identifiedLog(red("Heartbeat failed...")); + identifiedLog(red(e.message)); + } error = e; } finally { if (error) { @@ -145,13 +148,12 @@ async function checkHeartbeat() { writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); } } - identifiedLog(green("restarting heartbeater")); - setTimeout(checkHeartbeat, 1000 * frequency); + setTimeout(checkHeartbeat, 1000 * latency); } } async function startListening() { - identifiedLog(yellow(`After initialization, will poll server heartbeat every ${frequency} seconds...\n`)); + identifiedLog(yellow(`After initialization, will poll server heartbeat repeatedly...\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); -- cgit v1.2.3-70-g09d2 From 84f4cb0fcbf63ada120624526207ce3cefa68c3d Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 21:15:03 -0500 Subject: heartbeat --- src/server/ChildProcessUtilities/daemon/session.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/server') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 35c23b525..5155305ce 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -111,9 +111,9 @@ let current_backup: ChildProcess | undefined = undefined; async function checkHeartbeat() { let error: any; try { - count && identifiedLog(green("Requesting heartbeat")); + count && !restarting && process.stdout.write(green(`${identifier} <`)); await request.get(heartbeat); - count && identifiedLog(green("Received heartbeat")); + count && !restarting && console.log(green("3")); if (restarting || manualRestartActive) { addLogEntry(count++ ? "Backup server successfully restarted" : "Server successfully started", green); restarting = false; -- cgit v1.2.3-70-g09d2 From 22f2f7dd970f70bd8151762e31613029fdbb2f29 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 21:18:34 -0500 Subject: heartbeat --- src/server/ChildProcessUtilities/daemon/session.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/server') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 5155305ce..e2c72c1d5 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -120,6 +120,7 @@ async function checkHeartbeat() { } } catch (e) { if (count) { + console.log(); identifiedLog(red("Heartbeat failed...")); identifiedLog(red(e.message)); } -- cgit v1.2.3-70-g09d2 From c79923651eab568ec1be6eb542f937041c6e897c Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Thu, 12 Dec 2019 22:03:39 -0500 Subject: heartbeat --- src/server/ChildProcessUtilities/daemon/session.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'src/server') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index e2c72c1d5..8d2845cf3 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -111,19 +111,15 @@ let current_backup: ChildProcess | undefined = undefined; async function checkHeartbeat() { let error: any; try { - count && !restarting && process.stdout.write(green(`${identifier} <`)); + count && !restarting && process.stdout.write(`${identifier} 👂 `); await request.get(heartbeat); - count && !restarting && console.log(green("3")); + count && !restarting && console.log('⇠ 💚'); if (restarting || manualRestartActive) { addLogEntry(count++ ? "Backup server successfully restarted" : "Server successfully started", green); restarting = false; } } catch (e) { - if (count) { - console.log(); - identifiedLog(red("Heartbeat failed...")); - identifiedLog(red(e.message)); - } + count && !restarting && console.log("⇠ 💔"); error = e; } finally { if (error) { @@ -132,6 +128,7 @@ async function checkHeartbeat() { if (count && !manualRestartActive) { console.log(); addLogEntry("Detected a server crash", red); + identifiedLog(red(error.message)); await endPrevious(); await log_execution({ startMessage: identifier + " Sending crash notification email", -- cgit v1.2.3-70-g09d2 From a3a654ae509c03508916235664217731f5a91db2 Mon Sep 17 00:00:00 2001 From: Sam Wilkins <35748010+samwilkins333@users.noreply.github.com> Date: Fri, 13 Dec 2019 00:31:02 -0500 Subject: await end previous --- src/server/ChildProcessUtilities/daemon/session.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/server') diff --git a/src/server/ChildProcessUtilities/daemon/session.ts b/src/server/ChildProcessUtilities/daemon/session.ts index 8d2845cf3..fb2b3551e 100644 --- a/src/server/ChildProcessUtilities/daemon/session.ts +++ b/src/server/ChildProcessUtilities/daemon/session.ts @@ -19,7 +19,7 @@ createInterface(process.stdin, process.stdout).on('line', async line => { case "restart": manualRestartActive = true; identifiedLog(cyan("Initializing manual restart...")); - endPrevious(); + await endPrevious(); break; case "exit": identifiedLog(cyan("Initializing session end")); -- cgit v1.2.3-70-g09d2