diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/server/ActionUtilities.ts | 9 | ||||
-rw-r--r-- | src/server/ChildProcessUtilities/ProcessFactory.ts (renamed from src/server/ProcessManager.ts) | 67 | ||||
-rw-r--r-- | src/server/ChildProcessUtilities/daemon/persistence_daemon.ts (renamed from src/server/daemon/persistence_daemon.ts) | 58 | ||||
-rw-r--r-- | src/server/daemon/current_daemon_pid.txt | 1 | ||||
-rw-r--r-- | src/server/daemon/session_crashes_@ 2019-12-11T08:31:56.281Z.log | 1 | ||||
-rw-r--r-- | src/server/daemon/session_crashes_@ 2019-12-11T08:43:46.454Z.log | 2 | ||||
-rw-r--r-- | src/server/index.ts | 11 |
7 files changed, 91 insertions, 58 deletions
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<number>(resolve => logStream.on("open", resolve)); +} + export const command_line = (command: string, fromDirectory?: string) => { return new Promise<string>((resolve, reject) => { const options: ExecOptions = {}; @@ -54,7 +59,7 @@ export async function log_execution<T>({ 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/ProcessManager.ts b/src/server/ChildProcessUtilities/ProcessFactory.ts index 671f0a234..745b1479a 100644 --- a/src/server/ProcessManager.ts +++ b/src/server/ChildProcessUtilities/ProcessFactory.ts @@ -1,44 +1,28 @@ -import { existsSync, mkdirSync, createWriteStream } from "fs"; -import { pathFromRoot, log_execution } from './ActionUtilities'; +import { existsSync, mkdirSync } from "fs"; +import { pathFromRoot, log_execution, fileDescriptorFromStream } from '../ActionUtilities'; import { red, green } from "colors"; import rimraf = require("rimraf"); -import { ChildProcess, spawn } from "child_process"; +import { ChildProcess, spawn, StdioOptions } from "child_process"; import { Stream } from "stream"; +import { resolve } from "path"; -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<any>(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 namespace ProcessFactory { export type Sink = "pipe" | "ipc" | "ignore" | "inherit" | Stream | number | null | undefined; - export async function spawn_detached(command: string, args?: readonly string[], out?: Sink): Promise<ChildProcess> { - if (!out) { - const logStream = createWriteStream(generate_log_name(command, args)); - out = await new Promise<number>(resolve => logStream.on("open", resolve)); + export async function createWorker(command: string, args?: readonly string[], stdio?: StdioOptions | "logfile", detached = true): Promise<ChildProcess> { + if (stdio === "logfile") { + const log_fd = await Logger.create(command, args); + stdio = ["ignore", log_fd, log_fd]; } - const child = spawn(command, args, { detached: true, stdio: ["ignore", out, out] }); + const child = spawn(command, args, { detached, stdio }); child.unref(); return child; } - let daemonInitialized = false; - export async function trySpawnDaemon() { - if (!process.env.SPAWNED && !daemonInitialized) { - daemonInitialized = true; + export namespace NamedAgents { + + export async function persistenceDaemon() { await log_execution({ startMessage: "\ninitializing persistence daemon", endMessage: ({ result, error }) => { @@ -50,11 +34,34 @@ export namespace ProcessManager { } return "failsafe daemon process successfully spawned"; }, - action: () => spawn_detached('npx', ['ts-node', daemonPath], process.stdout), + 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<any>(resolve => rimraf(logPath, resolve)); + } + } + mkdirSync(logPath); + } + + export async function create(command: string, args?: readonly string[]): Promise<number> { + 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/daemon/persistence_daemon.ts b/src/server/ChildProcessUtilities/daemon/persistence_daemon.ts index 099c7898c..888cf38b8 100644 --- a/src/server/daemon/persistence_daemon.ts +++ b/src/server/ChildProcessUtilities/daemon/persistence_daemon.ts @@ -1,32 +1,52 @@ import * as request from "request-promise"; -import { log_execution, pathFromRoot } from "../ActionUtilities"; +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, createWriteStream, existsSync } from "fs"; +import { writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs"; import { resolve } from 'path'; import { ChildProcess } from "child_process"; -import { ProcessManager } from "../ProcessManager"; +import { ProcessFactory } from "../ProcessFactory"; -console.log(yellow("Initializing daemon...")); +const identifier = yellow("__daemon__:"); process.on('SIGINT', () => current_backup?.kill("SIGTERM")); -const crashLogPath = resolve(__dirname, `./session_crashes_${timestamp()}.log`); +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()}.`); - console.log(formatted); + 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"; -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`)); +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()}`; @@ -35,9 +55,9 @@ function timestamp() { let current_backup: ChildProcess | undefined = undefined; async function listen() { - console.log(yellow(`Beginning to poll server heartbeat every ${frequency} seconds...\n`)); + identifiedLog(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.")); + 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`; @@ -60,26 +80,26 @@ async function listen() { addLogEntry("Detected a server crash", red); current_backup?.kill(); await log_execution({ - startMessage: "Sending crash notification email", + startMessage: identifier + " Sending crash notification email", endMessage: ({ error, result }) => { const success = error === null && result === true; - return `${(success ? `Notification successfully sent to` : `Failed to notify`)} ${recipient} ${timestamp()}`; + 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: "Initiating server restart", + startMessage: identifier + " 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}`; + return identifier + success ? " Child process spawned..." : ` An error occurred while attempting to restart the server:\n${error}`; }, - action: () => ProcessManager.spawn_detached('npm', ['run', 'start-spawn']), + action: () => ProcessFactory.createWorker('npm', ['run', 'start-spawn'], "inherit"), color: green }); - writeFileSync(pathFromRoot("./logs/current_server_pid.txt"), `${current_backup?.pid ?? -1} created ${timestamp()}\n`); + writeLocalPidLog("server", `${(current_backup?.pid ?? -2) + 1} created ${timestamp()}`); } else { - console.log(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); + identifiedLog(yellow(`Callback ignored because restarting already initiated ${timestamp()}`)); } } } 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/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 @@ -[31mDetected a server crash @ 2019-12-11T08:44:26.494Z.[39m -[32mBackup server successfully restarted @ 2019-12-11T08:45:33.644Z.[39m 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"); } }); |