il2cpp-dump-analyzer-mcp
Version:
Agentic RAG system for analyzing IL2CPP dump.cs files from Unity games
498 lines • 16.8 kB
JavaScript
"use strict";
/**
* Container lifecycle management for IL2CPP dump analyzer MCP system
* Handles startup, shutdown, health monitoring, and restart policies
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.LifecycleManager = void 0;
const events_1 = require("events");
/**
* Container lifecycle management service
*/
class LifecycleManager extends events_1.EventEmitter {
constructor(config) {
super();
this.shutdownHandlers = [];
this.startupHandlers = [];
this.config = {
startupTimeout: 60000, // 1 minute
shutdownTimeout: 30000, // 30 seconds
healthCheckInterval: 30000, // 30 seconds
restartPolicy: 'on-failure',
maxRestarts: 5,
restartDelay: 5000, // 5 seconds
dependencyChecks: true,
gracefulShutdown: true,
...config
};
this.state = {
phase: 'stopped',
restartCount: 0,
isHealthy: false,
dependencies: this.initializeDependencies()
};
this.setupSignalHandlers();
}
/**
* Initialize dependency definitions
*/
initializeDependencies() {
return [
{
name: 'supabase-db',
status: 'unknown',
lastCheck: new Date(),
required: true,
timeout: 5000
},
{
name: 'supabase-rest',
status: 'unknown',
lastCheck: new Date(),
required: true,
checkUrl: process.env.SUPABASE_URL,
timeout: 5000
},
{
name: 'xenova-models',
status: 'unknown',
lastCheck: new Date(),
required: false,
timeout: 10000
}
];
}
/**
* Initialize lifecycle manager with services
*/
initialize(services) {
this.healthService = services.healthService;
this.metricsService = services.metricsService;
// Listen to health status changes
this.healthService.on('healthCheck', (status) => {
this.handleHealthStatusChange(status);
});
// Register shutdown handlers for services
this.healthService.onShutdown(async () => {
console.log('Shutting down health service...');
this.healthService?.stop();
});
this.metricsService.on('started', () => {
console.log('Metrics service started');
});
console.log('Lifecycle manager initialized');
}
/**
* Start the application lifecycle
*/
async start() {
if (this.state.phase === 'running' || this.state.phase === 'starting') {
console.warn('Application is already starting or running');
return;
}
console.log('Starting application lifecycle...');
this.setState({ phase: 'starting', startTime: new Date() });
try {
// Set startup timeout
const startupPromise = this.executeStartup();
const timeoutPromise = new Promise((_, reject) => {
this.startupTimer = setTimeout(() => {
reject(new Error(`Startup timeout after ${this.config.startupTimeout}ms`));
}, this.config.startupTimeout);
});
await Promise.race([startupPromise, timeoutPromise]);
// Clear startup timer
if (this.startupTimer) {
clearTimeout(this.startupTimer);
this.startupTimer = undefined;
}
this.setState({ phase: 'running', isHealthy: true });
console.log('Application started successfully');
this.emit('started');
}
catch (error) {
console.error('Application startup failed:', error);
this.setState({ phase: 'failed' });
this.emit('startupFailed', error);
// Handle restart policy
await this.handleRestartPolicy(error);
}
}
/**
* Execute startup sequence
*/
async executeStartup() {
console.log('Executing startup sequence...');
// Check dependencies first
if (this.config.dependencyChecks) {
await this.checkDependencies();
const requiredDepsHealthy = this.state.dependencies
.filter(dep => dep.required)
.every(dep => dep.status === 'healthy');
if (!requiredDepsHealthy) {
throw new Error('Required dependencies are not healthy');
}
}
// Execute startup handlers
for (const [index, handler] of this.startupHandlers.entries()) {
try {
console.log(`Executing startup handler ${index + 1}/${this.startupHandlers.length}`);
await handler();
}
catch (error) {
throw new Error(`Startup handler ${index + 1} failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
// Start services
if (this.healthService) {
this.healthService.start();
}
if (this.metricsService) {
this.metricsService.start();
}
// Start dependency monitoring
if (this.config.dependencyChecks) {
this.startDependencyMonitoring();
}
}
/**
* Stop the application lifecycle
*/
async stop() {
if (this.state.phase === 'stopped' || this.state.phase === 'stopping') {
console.warn('Application is already stopping or stopped');
return;
}
console.log('Stopping application lifecycle...');
this.setState({ phase: 'stopping' });
try {
if (this.config.gracefulShutdown) {
await this.executeGracefulShutdown();
}
else {
await this.executeImmediateShutdown();
}
this.setState({ phase: 'stopped', isHealthy: false });
console.log('Application stopped successfully');
this.emit('stopped');
}
catch (error) {
console.error('Application shutdown failed:', error);
this.setState({ phase: 'failed' });
this.emit('shutdownFailed', error);
}
}
/**
* Execute graceful shutdown
*/
async executeGracefulShutdown() {
console.log('Executing graceful shutdown...');
const shutdownPromise = this.executeShutdownSequence();
const timeoutPromise = new Promise((_, reject) => {
this.shutdownTimer = setTimeout(() => {
reject(new Error(`Shutdown timeout after ${this.config.shutdownTimeout}ms`));
}, this.config.shutdownTimeout);
});
try {
await Promise.race([shutdownPromise, timeoutPromise]);
}
finally {
if (this.shutdownTimer) {
clearTimeout(this.shutdownTimer);
this.shutdownTimer = undefined;
}
}
}
/**
* Execute shutdown sequence
*/
async executeShutdownSequence() {
// Stop dependency monitoring
this.stopDependencyMonitoring();
// Stop services
if (this.metricsService) {
this.metricsService.stop();
}
if (this.healthService) {
this.healthService.stop();
}
// Execute shutdown handlers
for (const [index, handler] of this.shutdownHandlers.entries()) {
try {
console.log(`Executing shutdown handler ${index + 1}/${this.shutdownHandlers.length}`);
await Promise.race([
handler(),
new Promise((_, reject) => setTimeout(() => reject(new Error('Handler timeout')), 5000))
]);
}
catch (error) {
console.error(`Shutdown handler ${index + 1} failed:`, error);
}
}
}
/**
* Execute immediate shutdown
*/
async executeImmediateShutdown() {
console.log('Executing immediate shutdown...');
// Stop all timers
this.stopDependencyMonitoring();
if (this.shutdownTimer) {
clearTimeout(this.shutdownTimer);
}
if (this.startupTimer) {
clearTimeout(this.startupTimer);
}
// Force stop services
this.metricsService?.stop();
this.healthService?.stop();
}
/**
* Handle health status changes
*/
handleHealthStatusChange(status) {
const wasHealthy = this.state.isHealthy;
const isHealthy = status.status === 'healthy';
this.setState({ isHealthy });
// Handle health status transitions
if (wasHealthy && !isHealthy) {
console.warn('Application became unhealthy');
this.emit('unhealthy', status);
// Consider restart based on policy
if (this.config.restartPolicy === 'on-failure' || this.config.restartPolicy === 'always') {
this.scheduleRestart('health check failure');
}
}
else if (!wasHealthy && isHealthy) {
console.log('Application became healthy');
this.emit('healthy', status);
}
}
/**
* Handle restart policy
*/
async handleRestartPolicy(error) {
if (this.config.restartPolicy === 'never') {
console.log('Restart policy is "never", not restarting');
return;
}
if (this.state.restartCount >= this.config.maxRestarts) {
console.error(`Maximum restart attempts (${this.config.maxRestarts}) reached`);
this.emit('maxRestartsReached');
return;
}
if (this.config.restartPolicy === 'on-failure' || this.config.restartPolicy === 'always') {
this.scheduleRestart(error);
}
}
/**
* Schedule a restart
*/
scheduleRestart(reason) {
if (this.restartTimer) {
return; // Restart already scheduled
}
console.log(`Scheduling restart in ${this.config.restartDelay}ms due to: ${reason}`);
this.restartTimer = setTimeout(async () => {
this.restartTimer = undefined;
await this.restart(reason);
}, this.config.restartDelay);
}
/**
* Restart the application
*/
async restart(reason) {
console.log(`Restarting application (attempt ${this.state.restartCount + 1}/${this.config.maxRestarts})`);
this.setState({
restartCount: this.state.restartCount + 1,
lastRestart: new Date()
});
this.emit('restarting', { reason, attempt: this.state.restartCount });
try {
await this.stop();
await new Promise(resolve => setTimeout(resolve, 1000)); // Brief pause
await this.start();
}
catch (error) {
console.error('Restart failed:', error);
this.emit('restartFailed', error);
}
}
/**
* Check dependencies health
*/
async checkDependencies() {
console.log('Checking dependencies...');
const checkPromises = this.state.dependencies.map(async (dep) => {
try {
const isHealthy = await this.checkDependency(dep);
dep.status = isHealthy ? 'healthy' : 'unhealthy';
dep.lastCheck = new Date();
}
catch (error) {
console.warn(`Dependency check failed for ${dep.name}:`, error);
dep.status = 'unhealthy';
dep.lastCheck = new Date();
}
});
await Promise.all(checkPromises);
const healthyCount = this.state.dependencies.filter(d => d.status === 'healthy').length;
console.log(`Dependencies: ${healthyCount}/${this.state.dependencies.length} healthy`);
}
/**
* Check individual dependency
*/
async checkDependency(dep) {
// Implement specific dependency checks based on type
switch (dep.name) {
case 'supabase-db':
return this.checkDatabaseDependency(dep);
case 'supabase-rest':
return this.checkRestApiDependency(dep);
case 'xenova-models':
return this.checkModelsDependency(dep);
default:
return true; // Unknown dependencies are considered healthy
}
}
/**
* Check database dependency
*/
async checkDatabaseDependency(dep) {
try {
// Use health service to check database if available
if (this.healthService) {
const status = await this.healthService.getHealthStatus();
const dbComponent = status.components.find(c => c.name === 'database');
return dbComponent?.status === 'healthy';
}
return true;
}
catch (error) {
console.warn(`Database dependency check failed:`, error);
return false;
}
}
/**
* Check REST API dependency
*/
async checkRestApiDependency(dep) {
if (!dep.checkUrl) {
return true;
}
try {
// Simple HTTP health check
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), dep.timeout);
const response = await fetch(`${dep.checkUrl}/health`, {
method: 'GET',
signal: controller.signal,
headers: { 'User-Agent': 'IL2CPP-MCP-HealthCheck/1.0' }
});
clearTimeout(timeoutId);
return response.ok;
}
catch (error) {
console.warn(`REST API dependency check failed:`, error);
return false;
}
}
/**
* Check models dependency (Xenova models availability)
*/
async checkModelsDependency(dep) {
try {
// Check if model cache directory exists and is accessible
const fs = await import('fs/promises');
const modelCachePath = process.env.MODEL_CACHE_PATH || '/app/models';
await fs.access(modelCachePath);
return true;
}
catch (error) {
console.warn(`Models dependency check failed:`, error);
return false;
}
}
/**
* Start dependency monitoring
*/
startDependencyMonitoring() {
if (this.dependencyCheckInterval) {
return;
}
console.log('Starting dependency monitoring...');
this.dependencyCheckInterval = setInterval(async () => {
await this.checkDependencies();
}, this.config.healthCheckInterval);
}
/**
* Stop dependency monitoring
*/
stopDependencyMonitoring() {
if (this.dependencyCheckInterval) {
clearInterval(this.dependencyCheckInterval);
this.dependencyCheckInterval = undefined;
console.log('Dependency monitoring stopped');
}
}
/**
* Setup signal handlers
*/
setupSignalHandlers() {
const signals = ['SIGTERM', 'SIGINT', 'SIGUSR2'];
signals.forEach(signal => {
process.on(signal, async () => {
console.log(`Received ${signal}, initiating graceful shutdown...`);
await this.stop();
process.exit(0);
});
});
}
/**
* Update lifecycle state
*/
setState(updates) {
this.state = { ...this.state, ...updates };
this.emit('stateChanged', this.state);
}
/**
* Register startup handler
*/
onStartup(handler) {
this.startupHandlers.push(handler);
}
/**
* Register shutdown handler
*/
onShutdown(handler) {
this.shutdownHandlers.push(handler);
}
/**
* Get current lifecycle state
*/
getState() {
return { ...this.state };
}
/**
* Get lifecycle configuration
*/
getConfig() {
return { ...this.config };
}
/**
* Force restart (manual trigger)
*/
async forceRestart(reason = 'manual trigger') {
console.log(`Force restart requested: ${reason}`);
await this.restart(reason);
}
/**
* Reset restart counter
*/
resetRestartCounter() {
this.setState({ restartCount: 0 });
console.log('Restart counter reset');
}
}
exports.LifecycleManager = LifecycleManager;
//# sourceMappingURL=lifecycle-manager.js.map