ordojs
Version:
A revolutionary web framework with compile-time optimizations and unified client-server development
191 lines • 6.46 kB
JavaScript
/**
* @fileoverview OrdoJS CLI - Process Manager
*
* Handles process lifecycle management, cleanup, and zombie prevention.
*/
import { ChildProcess, spawn } from 'child_process';
import { logger } from '../utils/index.js';
/**
* ProcessManager class for managing child processes
*/
export class ProcessManager {
processes;
isShuttingDown;
exitHandlerRegistered;
/**
* Create a new ProcessManager instance
*/
constructor() {
this.processes = new Map();
this.isShuttingDown = false;
this.exitHandlerRegistered = false;
this.registerExitHandlers();
}
/**
* Register handlers for process exit events
*/
registerExitHandlers() {
if (this.exitHandlerRegistered) {
return;
}
// Handle normal exit
process.on('exit', () => {
this.cleanup();
});
// Handle Ctrl+C and other signals
process.on('SIGINT', () => {
this.shutdown('SIGINT received');
});
process.on('SIGTERM', () => {
this.shutdown('SIGTERM received');
});
// Handle uncaught exceptions
process.on('uncaughtException', (error) => {
logger.error(`Uncaught exception: ${error.message}`);
this.shutdown('Uncaught exception');
});
this.exitHandlerRegistered = true;
}
/**
* Start a new child process
*
* @param id - Unique identifier for the process
* @param command - Command to execute
* @param args - Command arguments
* @param options - Spawn options
* @returns The created child process
*/
startProcess(id, command, args = [], options = {}) {
if (this.isShuttingDown) {
throw new Error('Cannot start new process during shutdown');
}
logger.debug(`Starting process ${id}: ${command} ${args.join(' ')}`);
const childProcess = spawn(command, args, {
stdio: 'pipe',
...options
});
// Store the process
this.processes.set(id, childProcess);
// Set up event handlers
childProcess.on('error', (error) => {
logger.error(`Process ${id} error: ${error.message}`);
});
childProcess.on('exit', (code, signal) => {
logger.debug(`Process ${id} exited with code ${code} and signal ${signal}`);
this.processes.delete(id);
});
// Handle stdout and stderr
if (childProcess.stdout) {
childProcess.stdout.on('data', (data) => {
logger.debug(`[${id}] ${data.toString().trim()}`);
});
}
if (childProcess.stderr) {
childProcess.stderr.on('data', (data) => {
logger.error(`[${id}] ${data.toString().trim()}`);
});
}
return childProcess;
}
/**
* Stop a specific process
*
* @param id - Process identifier
* @param signal - Signal to send (default: SIGTERM)
* @returns Promise that resolves when the process has exited
*/
async stopProcess(id, signal = 'SIGTERM') {
const childProcess = this.processes.get(id);
if (!childProcess) {
logger.debug(`Process ${id} not found or already stopped`);
return;
}
return new Promise((resolve) => {
// Set up exit handler
childProcess.once('exit', () => {
this.processes.delete(id);
logger.debug(`Process ${id} stopped`);
resolve();
});
// Try to kill the process gracefully
if (childProcess.kill(signal)) {
logger.debug(`Sent ${signal} to process ${id}`);
}
else {
logger.warn(`Failed to send ${signal} to process ${id}, may already be exiting`);
this.processes.delete(id);
resolve();
}
// Set a timeout to force kill if it doesn't exit
setTimeout(() => {
if (this.processes.has(id)) {
logger.warn(`Process ${id} did not exit after ${signal}, forcing SIGKILL`);
childProcess.kill('SIGKILL');
}
}, 5000);
});
}
/**
* Check if a process is running
*
* @param id - Process identifier
* @returns True if the process is running
*/
isProcessRunning(id) {
return this.processes.has(id);
}
/**
* Get all running processes
*
* @returns Map of process IDs to child processes
*/
getRunningProcesses() {
return new Map(this.processes);
}
/**
* Clean up all managed processes
*/
async cleanup() {
if (this.processes.size === 0) {
return;
}
logger.debug(`Cleaning up ${this.processes.size} processes`);
// Create an array of promises for stopping each process
const stopPromises = Array.from(this.processes.keys()).map(id => this.stopProcess(id).catch(error => {
logger.error(`Error stopping process ${id}: ${error instanceof Error ? error.message : String(error)}`);
}));
// Wait for all processes to stop
await Promise.all(stopPromises);
// Double-check that all processes are gone
if (this.processes.size > 0) {
logger.warn(`${this.processes.size} processes could not be stopped gracefully, forcing termination`);
// Force kill any remaining processes
for (const [id, childProcess] of this.processes.entries()) {
childProcess.kill('SIGKILL');
this.processes.delete(id);
}
}
}
/**
* Shutdown the process manager and all managed processes
*
* @param reason - Reason for shutdown
*/
async shutdown(reason) {
if (this.isShuttingDown) {
return;
}
this.isShuttingDown = true;
logger.info(`Shutting down: ${reason}`);
try {
await this.cleanup();
logger.success('All processes terminated successfully');
}
catch (error) {
logger.error(`Error during shutdown: ${error instanceof Error ? error.message : String(error)}`);
}
// Exit the process
process.exit(0);
}
}
//# sourceMappingURL=process-manager.js.map