UNPKG

@comake/skl-js-engine

Version:

Standard Knowledge Language Javascript Engine

219 lines 7.79 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProcessManager = void 0; const node_events_1 = require("node:events"); const logger_1 = require("../../../logger"); const constants_1 = require("../../constants"); const denoUtils_1 = require("../../denoUtils"); const errors_1 = require("../../errors"); const PermissionBuilder_1 = require("../../PermissionBuilder"); const PollingUtils_1 = require("../utils/PollingUtils"); /** * Manages the lifecycle of child processes for JavaScript execution */ class ProcessManager extends node_events_1.EventEmitter { constructor() { super(); this.logger = logger_1.Logger.getInstance(); } /** * Set a name for this process manager (used in logging) */ setName(name) { this.name = name; this.logger.setMetadata({ name, component: 'ProcessManager' }); } /** * Get the current child process */ getProcess() { return this.childProcess; } /** * Check if the process is running */ isRunning() { return this.childProcess !== undefined && !this.childProcess.killed; } /** * Spawn a new child process */ async spawn(config) { if (this.childProcess) { throw new Error('Process already spawned. Call terminate() first.'); } try { const normalizedOptions = this.normalizeExecutionOptions(config.executionOptions); const permissionConfig = this.extractPermissionConfig(normalizedOptions); // Validate permissions before proceeding (0, PermissionBuilder_1.validatePermissionConfig)(permissionConfig); const permissions = (0, PermissionBuilder_1.buildDenoPermissions)(permissionConfig); // Build command arguments const commandArgs = [...constants_1.EXECUTION_CONSTANTS.denoFlags, ...permissions, config.scriptPath]; this.logger.debug('Spawning process with args:', commandArgs); // Spawn the Deno process this.childProcess = (0, denoUtils_1.spawnDenoProcess)(commandArgs); this.setupProcessHandlers(); // Wait for process to be ready await this.waitForReady(config.startupTimeout ?? 60 * 60 * 1000); this.emit('ready'); } catch (error) { this.childProcess = undefined; throw error; } } /** * Terminate the child process */ async terminate(timeout = 3000) { if (!this.childProcess || this.childProcess.killed) { return; } this.logger.log('Terminating process'); // First try graceful termination this.childProcess.kill(constants_1.EXECUTION_CONSTANTS.processSignals.term); // Wait for process to exit gracefully await new Promise(resolve => { const timeoutId = setTimeout(() => { if (this.childProcess && !this.childProcess.killed) { this.logger.log('Force killing process after timeout'); this.childProcess.kill('SIGKILL'); } resolve(); }, timeout); this.childProcess.on('exit', () => { clearTimeout(timeoutId); resolve(); }); }); this.childProcess = undefined; } /** * Send data to the process stdin */ write(data) { if (!this.childProcess?.stdin) { throw new Error('Process stdin not available'); } return this.childProcess.stdin.write(data); } /** * Get process stdout stream */ getStdout() { return this.childProcess?.stdout ?? undefined; } /** * Get process stdin stream */ getStdin() { return this.childProcess?.stdin ?? undefined; } /** * Register event listeners */ on(event, handler) { return super.on(event, handler); } /** * Remove event listeners */ off(event, handler) { return super.off(event, handler); } /** * Set up process event handlers */ setupProcessHandlers() { if (!this.childProcess) { return; } // Handle stdout data this.childProcess.stdout?.on('data', (data) => { this.emit('stdout', data); }); // Handle stderr data this.childProcess.stderr?.on('data', (data) => { this.emit('stderr', data); }); // Handle process exit this.childProcess.on('exit', (code, signal) => { this.logger.log(`Process exited with code: ${code}, signal: ${signal}`); this.emit('exit', code, signal); }); // Handle process errors this.childProcess.on('error', error => { this.logger.error('Process error:', error); this.emit('error', new errors_1.ProcessSpawnError(error)); }); } /** * Wait for the process to be ready */ async waitForReady(timeout) { await (0, PollingUtils_1.pollUntilTrue)(() => { // Check if process is running and not killed if (!this.childProcess) { // Process not spawned yet return false; } if (this.childProcess.killed) { throw new Error('Process was killed during startup'); } // Process is running return !this.childProcess.killed; }, { timeout, // Check every 100ms or 1/10th of timeout interval: Math.min(100, timeout / 10), // Let process start before first check initialDelay: 50 }, 'Process startup timed out'); // Additional wait for process to be fully ready (like the original logic) const stabilityWait = Math.min(500, timeout / 4); await new Promise(resolve => setTimeout(resolve, stabilityWait)); // Final check that process is still running after stability wait if (!this.childProcess || this.childProcess.killed) { throw new Error('Process died during startup after initial success'); } } /** * Normalize execution options with defaults */ normalizeExecutionOptions(options) { const DEFAULT_EXECUTION_OPTIONS = { timeout: 30000, functionName: 'main', allowNetwork: false, allowedDomains: [], allowEnv: false, allowRead: false, debugMode: false, retries: 0 }; return { timeout: options?.timeout ?? DEFAULT_EXECUTION_OPTIONS.timeout, functionName: options?.functionName ?? DEFAULT_EXECUTION_OPTIONS.functionName, allowNetwork: options?.allowNetwork ?? DEFAULT_EXECUTION_OPTIONS.allowNetwork, allowedDomains: options?.allowedDomains ?? [...DEFAULT_EXECUTION_OPTIONS.allowedDomains], allowEnv: options?.allowEnv ?? DEFAULT_EXECUTION_OPTIONS.allowEnv, allowRead: options?.allowRead ?? DEFAULT_EXECUTION_OPTIONS.allowRead, debugMode: options?.debugMode ?? DEFAULT_EXECUTION_OPTIONS.debugMode, retries: options?.retries ?? DEFAULT_EXECUTION_OPTIONS.retries }; } /** * Extract permission configuration from execution options */ extractPermissionConfig(options) { return { allowNetwork: options.allowNetwork, allowedDomains: options.allowedDomains, allowEnv: options.allowEnv, allowRead: options.allowRead }; } } exports.ProcessManager = ProcessManager; //# sourceMappingURL=ProcessManager.js.map