snow-flow
Version:
Snow-Flow v3.2.0: Complete ServiceNow Enterprise Suite with 180+ MCP Tools. ATF Testing, Knowledge Management, Service Catalog, Change Management with CAB scheduling, Virtual Agent chatbots with NLU, Performance Analytics KPIs, Flow Designer automation, A
319 lines • 12.3 kB
JavaScript
;
/**
* MCP Process Manager - SAFE VERSION
* Emergency fix for memory crash issues
* Implements graceful shutdown and memory-safe cleanup
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.mcpProcessManager = exports.MCPProcessManager = void 0;
const child_process_1 = require("child_process");
const logger_js_1 = require("./logger.js");
const util_1 = require("util");
const execAsync = (0, util_1.promisify)(child_process_1.exec);
const logger = new logger_js_1.Logger('MCPProcessManager');
class MCPProcessManager {
constructor() {
// INCREASED LIMITS TO PREVENT AGGRESSIVE CLEANUP
this.MAX_MCP_SERVERS = parseInt(process.env.SNOW_MAX_MCP_SERVERS || '30'); // Increased from 10
this.MAX_MEMORY_MB = parseInt(process.env.SNOW_MCP_MEMORY_LIMIT || '3000'); // Increased from 1500
// DISABLED AUTOMATIC CLEANUP BY DEFAULT
this.CLEANUP_ENABLED = process.env.SNOW_MCP_CLEANUP_ENABLED === 'true'; // Off by default
this.CLEANUP_INTERVAL = parseInt(process.env.SNOW_MCP_CLEANUP_INTERVAL || '300000'); // 5 minutes instead of 1
this.isCleaningUp = false;
// Only start cleanup if explicitly enabled
if (this.CLEANUP_ENABLED) {
logger.warn('⚠️ MCP cleanup is ENABLED - monitor for memory issues');
this.startPeriodicCleanup();
}
else {
logger.info('✅ MCP cleanup is DISABLED for stability');
}
}
static getInstance() {
if (!MCPProcessManager.instance) {
MCPProcessManager.instance = new MCPProcessManager();
}
return MCPProcessManager.instance;
}
/**
* Check if we can spawn a new MCP server
*/
canSpawnServer() {
const status = this.getSystemStatus();
// More lenient limits
if (status.processCount >= this.MAX_MCP_SERVERS) {
logger.warn(`⚠️ At server limit (${status.processCount}/${this.MAX_MCP_SERVERS}) - consider manual cleanup`);
// Still allow spawning unless critically high
if (status.processCount >= this.MAX_MCP_SERVERS * 1.5) {
return false;
}
}
if (status.memoryUsageMB > this.MAX_MEMORY_MB) {
logger.warn(`⚠️ High memory usage (${status.memoryUsageMB}MB > ${this.MAX_MEMORY_MB}MB)`);
// Still allow spawning unless critically high
if (status.memoryUsageMB > this.MAX_MEMORY_MB * 1.5) {
return false;
}
}
return true;
}
/**
* Get current MCP system status - SAFER VERSION
*/
getSystemStatus() {
try {
// More careful process detection
const psOutput = (0, child_process_1.execSync)('ps aux | grep -E "mcp|servicenow.*mcp" | grep -v grep || true', {
encoding: 'utf8',
maxBuffer: 1024 * 1024 // 1MB buffer limit
}).trim();
if (!psOutput) {
return {
processCount: 0,
memoryUsageMB: 0,
processes: []
};
}
const lines = psOutput.split('\n').slice(0, 100); // Limit to 100 processes
const processes = [];
let totalMemory = 0;
for (const line of lines) {
try {
const parts = line.split(/\s+/);
if (parts.length > 10) {
const pid = parseInt(parts[1]);
const memory = Math.round(parseInt(parts[5]) / 1024); // Convert KB to MB
const name = parts.slice(10).join(' ').substring(0, 100); // Limit name length
if (!isNaN(pid) && !isNaN(memory)) {
processes.push({ pid, memory, name });
totalMemory += memory;
}
}
}
catch (e) {
// Skip malformed lines
}
}
return {
processCount: processes.length,
memoryUsageMB: totalMemory,
processes
};
}
catch (error) {
logger.error('Failed to get system status:', error);
return {
processCount: 0,
memoryUsageMB: 0,
processes: []
};
}
}
/**
* Gracefully shutdown a process with timeout
*/
async gracefulKill(pid, name) {
try {
// First try SIGTERM for graceful shutdown
process.kill(pid, 'SIGTERM');
// Wait up to 5 seconds for graceful shutdown
let waited = 0;
while (waited < 5000) {
try {
process.kill(pid, 0); // Check if still alive
await new Promise(resolve => setTimeout(resolve, 500));
waited += 500;
}
catch {
// Process terminated
logger.info(`✅ Gracefully stopped ${name} (PID: ${pid})`);
return true;
}
}
// Force kill if still alive
process.kill(pid, 'SIGKILL');
logger.warn(`⚠️ Force killed ${name} (PID: ${pid})`);
return true;
}
catch (error) {
if (error.code === 'ESRCH') {
// Process already dead
return true;
}
logger.error(`Failed to kill ${name} (PID: ${pid}):`, error);
return false;
}
}
/**
* Kill duplicate MCP servers - SAFER VERSION
*/
async killDuplicates() {
if (this.isCleaningUp) {
logger.warn('Cleanup already in progress, skipping...');
return;
}
this.isCleaningUp = true;
try {
const status = this.getSystemStatus();
// Group processes by server type
const serverGroups = new Map();
for (const proc of status.processes) {
const match = proc.name.match(/servicenow-([^-]+)-mcp\.js/);
if (match) {
const serverType = match[1];
if (!serverGroups.has(serverType)) {
serverGroups.set(serverType, []);
}
serverGroups.get(serverType).push({
pid: proc.pid,
memory: proc.memory
});
}
}
// Kill duplicates gracefully
for (const [serverType, procs] of serverGroups) {
if (procs.length > 2) { // Only clean if more than 2 duplicates
logger.info(`Found ${procs.length} instances of ${serverType}-mcp`);
// Sort by memory usage (kill highest consumers first)
procs.sort((a, b) => b.memory - a.memory);
// Keep 2 instances, kill the rest
for (let i = 2; i < procs.length; i++) {
await this.gracefulKill(procs[i].pid, `${serverType}-mcp`);
// Wait between kills to avoid memory spike
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
}
finally {
this.isCleaningUp = false;
}
}
/**
* Emergency cleanup - only for critical situations
*/
async emergencyCleanup() {
logger.warn('🚨 EMERGENCY CLEANUP INITIATED');
const status = this.getSystemStatus();
if (status.memoryUsageMB > this.MAX_MEMORY_MB * 2) {
logger.error(`🔴 CRITICAL: Memory usage ${status.memoryUsageMB}MB - killing highest consumers`);
// Sort by memory usage
const sorted = status.processes.sort((a, b) => b.memory - a.memory);
// Kill top 3 memory consumers
for (let i = 0; i < Math.min(3, sorted.length); i++) {
await this.gracefulKill(sorted[i].pid, sorted[i].name);
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2s between kills
}
// Force garbage collection if available
if (global.gc) {
global.gc();
logger.info('Forced garbage collection');
}
}
}
/**
* Safe cleanup - only when absolutely necessary
*/
async cleanup() {
if (!this.CLEANUP_ENABLED) {
logger.info('Cleanup disabled for stability');
return;
}
if (this.isCleaningUp) {
logger.warn('Cleanup already in progress');
return;
}
const status = this.getSystemStatus();
// Only cleanup if REALLY necessary
if (status.processCount > this.MAX_MCP_SERVERS * 1.5) {
logger.warn(`🧹 Too many MCP servers (${status.processCount}), cleaning duplicates...`);
await this.killDuplicates();
}
if (status.memoryUsageMB > this.MAX_MEMORY_MB * 1.5) {
await this.emergencyCleanup();
}
}
/**
* Start periodic cleanup - MUCH SAFER
*/
startPeriodicCleanup() {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
}
// Only run cleanup when critically necessary
this.cleanupTimer = setInterval(async () => {
try {
const status = this.getSystemStatus();
// Only cleanup if CRITICALLY high
if (status.processCount > this.MAX_MCP_SERVERS * 2 ||
status.memoryUsageMB > this.MAX_MEMORY_MB * 2) {
logger.warn('🔄 Critical resource usage detected, running cleanup...');
await this.cleanup();
}
}
catch (error) {
logger.error('Cleanup failed:', error);
}
}, this.CLEANUP_INTERVAL);
// Don't block process exit
this.cleanupTimer.unref();
}
/**
* Stop periodic cleanup
*/
stopPeriodicCleanup() {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = undefined;
logger.info('✅ Periodic cleanup stopped');
}
}
/**
* Kill all MCP servers - USE WITH CAUTION
*/
async killAll() {
logger.warn('🔴 KILLING ALL MCP PROCESSES');
try {
// First try graceful shutdown
await execAsync('pkill -TERM -f mcp');
await new Promise(resolve => setTimeout(resolve, 2000));
// Then force kill any remaining
await execAsync('pkill -KILL -f mcp');
logger.info('✅ All MCP processes terminated');
}
catch (error) {
// pkill returns non-zero if no processes found
logger.info('No MCP processes to kill');
}
}
/**
* Get resource usage summary
*/
getResourceSummary() {
const status = this.getSystemStatus();
return `MCP Resources:
Processes: ${status.processCount}/${this.MAX_MCP_SERVERS} (${Math.round(status.processCount / this.MAX_MCP_SERVERS * 100)}%)
Memory: ${status.memoryUsageMB}MB/${this.MAX_MEMORY_MB}MB (${Math.round(status.memoryUsageMB / this.MAX_MEMORY_MB * 100)}%)
Cleanup: ${this.CLEANUP_ENABLED ? 'ENABLED' : 'DISABLED'}
Status: ${this.getHealthStatus()}`;
}
/**
* Get health status
*/
getHealthStatus() {
const status = this.getSystemStatus();
const processPercent = status.processCount / this.MAX_MCP_SERVERS;
const memoryPercent = status.memoryUsageMB / this.MAX_MEMORY_MB;
if (processPercent > 1.5 || memoryPercent > 1.5) {
return 'critical';
}
if (processPercent > 1.0 || memoryPercent > 1.0) {
return 'warning';
}
return 'healthy';
}
}
exports.MCPProcessManager = MCPProcessManager;
// Export singleton instance
exports.mcpProcessManager = MCPProcessManager.getInstance();
//# sourceMappingURL=mcp-process-manager.js.map