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 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