feat(core): Introduced process management features using ProcessWrapper and enhanced configuration.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { ProcessWrapper } from './classes.processwrapper.js';
|
||||
|
||||
export interface IMonitorConfig {
|
||||
name?: string; // Optional name to identify the instance
|
||||
@@ -7,13 +8,16 @@ export interface IMonitorConfig {
|
||||
args?: string[]; // Optional: arguments for the command
|
||||
memoryLimitBytes: number; // Maximum allowed memory (in bytes) for the process group
|
||||
monitorIntervalMs?: number; // Interval (in ms) at which memory is checked (default: 5000)
|
||||
env?: NodeJS.ProcessEnv; // Optional: custom environment variables
|
||||
logBufferSize?: number; // Optional: number of log lines to keep (default: 100)
|
||||
}
|
||||
|
||||
export class ProcessMonitor {
|
||||
private child: plugins.childProcess.ChildProcess | null = null;
|
||||
private processWrapper: ProcessWrapper | null = null;
|
||||
private config: IMonitorConfig;
|
||||
private intervalId: NodeJS.Timeout | null = null;
|
||||
private stopped: boolean = true; // Initially stopped until start() is called
|
||||
private restartCount: number = 0;
|
||||
|
||||
constructor(config: IMonitorConfig) {
|
||||
this.config = config;
|
||||
@@ -23,59 +27,64 @@ export class ProcessMonitor {
|
||||
// Reset the stopped flag so that new processes can spawn.
|
||||
this.stopped = false;
|
||||
this.log(`Starting process monitor.`);
|
||||
this.spawnChild();
|
||||
this.spawnProcess();
|
||||
|
||||
// Set the monitoring interval.
|
||||
const interval = this.config.monitorIntervalMs || 5000;
|
||||
this.intervalId = setInterval(() => {
|
||||
if (this.child && this.child.pid) {
|
||||
this.monitorProcessGroup(this.child.pid, this.config.memoryLimitBytes);
|
||||
if (this.processWrapper && this.processWrapper.getPid()) {
|
||||
this.monitorProcessGroup(this.processWrapper.getPid()!, this.config.memoryLimitBytes);
|
||||
}
|
||||
}, interval);
|
||||
}
|
||||
|
||||
private spawnChild(): void {
|
||||
private spawnProcess(): void {
|
||||
// Don't spawn if the monitor has been stopped.
|
||||
if (this.stopped) return;
|
||||
|
||||
if (this.config.args && this.config.args.length > 0) {
|
||||
this.log(
|
||||
`Spawning command "${this.config.command}" with args [${this.config.args.join(
|
||||
', '
|
||||
)}] in directory: ${this.config.projectDir}`
|
||||
);
|
||||
this.child = plugins.childProcess.spawn(this.config.command, this.config.args, {
|
||||
cwd: this.config.projectDir,
|
||||
detached: true,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} else {
|
||||
this.log(
|
||||
`Spawning command "${this.config.command}" in directory: ${this.config.projectDir}`
|
||||
);
|
||||
// Use shell mode to allow a full command string.
|
||||
this.child = plugins.childProcess.spawn(this.config.command, {
|
||||
cwd: this.config.projectDir,
|
||||
detached: true,
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
});
|
||||
}
|
||||
// Create a new process wrapper
|
||||
this.processWrapper = new ProcessWrapper({
|
||||
name: this.config.name || 'unnamed-process',
|
||||
command: this.config.command,
|
||||
args: this.config.args,
|
||||
cwd: this.config.projectDir,
|
||||
env: this.config.env,
|
||||
logBuffer: this.config.logBufferSize,
|
||||
});
|
||||
|
||||
this.log(`Spawned process with PID ${this.child.pid}`);
|
||||
|
||||
// When the child process exits, restart it if the monitor isn't stopped.
|
||||
this.child.on('exit', (code, signal) => {
|
||||
this.log(`Child process exited with code ${code}, signal ${signal}.`);
|
||||
if (!this.stopped) {
|
||||
this.log('Restarting process...');
|
||||
this.spawnChild();
|
||||
// Set up event handlers
|
||||
this.processWrapper.on('log', (log) => {
|
||||
// Here we could add handlers to send logs somewhere
|
||||
// For now, we just log system messages to the console
|
||||
if (log.type === 'system') {
|
||||
this.log(log.message);
|
||||
}
|
||||
});
|
||||
|
||||
this.processWrapper.on('exit', (code, signal) => {
|
||||
this.log(`Process exited with code ${code}, signal ${signal}.`);
|
||||
if (!this.stopped) {
|
||||
this.log('Restarting process...');
|
||||
this.restartCount++;
|
||||
this.spawnProcess();
|
||||
}
|
||||
});
|
||||
|
||||
this.processWrapper.on('error', (error) => {
|
||||
this.log(`Process error: ${error.message}`);
|
||||
if (!this.stopped) {
|
||||
this.log('Restarting process due to error...');
|
||||
this.restartCount++;
|
||||
this.spawnProcess();
|
||||
}
|
||||
});
|
||||
|
||||
// Start the process
|
||||
this.processWrapper.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitor the process group’s memory usage. If the total memory exceeds the limit,
|
||||
* Monitor the process group's memory usage. If the total memory exceeds the limit,
|
||||
* kill the process group so that the 'exit' handler can restart it.
|
||||
*/
|
||||
private async monitorProcessGroup(pid: number, memoryLimit: number): Promise<void> {
|
||||
@@ -92,8 +101,10 @@ export class ProcessMonitor {
|
||||
memoryUsage
|
||||
)} exceeds limit of ${this.humanReadableBytes(memoryLimit)}. Restarting process.`
|
||||
);
|
||||
// Kill the entire process group by sending a signal to -PID.
|
||||
process.kill(-pid, 'SIGKILL');
|
||||
// Stop the process wrapper, which will trigger the exit handler and restart
|
||||
if (this.processWrapper) {
|
||||
this.processWrapper.stop();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.log('Error monitoring process group: ' + error);
|
||||
@@ -142,10 +153,48 @@ export class ProcessMonitor {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
}
|
||||
if (this.child && this.child.pid) {
|
||||
process.kill(-this.child.pid, 'SIGKILL');
|
||||
if (this.processWrapper) {
|
||||
this.processWrapper.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current logs from the process
|
||||
*/
|
||||
public getLogs(limit?: number): Array<{ timestamp: Date, type: string, message: string }> {
|
||||
if (!this.processWrapper) {
|
||||
return [];
|
||||
}
|
||||
return this.processWrapper.getLogs(limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of times the process has been restarted
|
||||
*/
|
||||
public getRestartCount(): number {
|
||||
return this.restartCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the process ID if running
|
||||
*/
|
||||
public getPid(): number | null {
|
||||
return this.processWrapper?.getPid() || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get process uptime in milliseconds
|
||||
*/
|
||||
public getUptime(): number {
|
||||
return this.processWrapper?.getUptime() || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the process is currently running
|
||||
*/
|
||||
public isRunning(): boolean {
|
||||
return this.processWrapper?.isRunning() || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for logging messages with the instance name.
|
||||
@@ -154,4 +203,4 @@ export class ProcessMonitor {
|
||||
const prefix = this.config.name ? `[${this.config.name}] ` : '';
|
||||
console.log(prefix + message);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user