You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
207 lines
5.3 KiB
207 lines
5.3 KiB
/**
|
|
* Service Initialization Manager
|
|
*
|
|
* Manages the proper initialization order of services to prevent race conditions
|
|
* and ensure dependencies are available when services are created.
|
|
*
|
|
* @author Matthew Raymer
|
|
* @since 2025-08-25
|
|
*/
|
|
|
|
import { logger } from "../utils/logger";
|
|
|
|
/**
|
|
* Service initialization status tracking
|
|
*/
|
|
interface ServiceStatus {
|
|
name: string;
|
|
initialized: boolean;
|
|
dependencies: string[];
|
|
error?: string;
|
|
}
|
|
|
|
/**
|
|
* Service initialization manager to prevent race conditions
|
|
*/
|
|
export class ServiceInitializationManager {
|
|
private static instance: ServiceInitializationManager;
|
|
private serviceStatuses = new Map<string, ServiceStatus>();
|
|
private initializationPromise: Promise<void> | null = null;
|
|
|
|
private constructor() {}
|
|
|
|
/**
|
|
* Get singleton instance
|
|
*/
|
|
static getInstance(): ServiceInitializationManager {
|
|
if (!ServiceInitializationManager.instance) {
|
|
ServiceInitializationManager.instance =
|
|
new ServiceInitializationManager();
|
|
}
|
|
return ServiceInitializationManager.instance;
|
|
}
|
|
|
|
/**
|
|
* Register a service that needs initialization
|
|
*/
|
|
registerService(name: string, dependencies: string[] = []): void {
|
|
this.serviceStatuses.set(name, {
|
|
name,
|
|
initialized: false,
|
|
dependencies,
|
|
});
|
|
|
|
logger.debug("[ServiceInit] 🔧 Service registered:", {
|
|
name,
|
|
dependencies,
|
|
totalServices: this.serviceStatuses.size,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mark a service as initialized
|
|
*/
|
|
markInitialized(name: string): void {
|
|
const status = this.serviceStatuses.get(name);
|
|
if (status) {
|
|
status.initialized = true;
|
|
logger.debug("[ServiceInit] ✅ Service initialized:", {
|
|
name,
|
|
totalInitialized: this.getInitializedCount(),
|
|
totalServices: this.serviceStatuses.size,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark a service as failed
|
|
*/
|
|
markFailed(name: string, error: string): void {
|
|
const status = this.serviceStatuses.get(name);
|
|
if (status) {
|
|
status.error = error;
|
|
logger.error("[ServiceInit] ❌ Service failed:", {
|
|
name,
|
|
error,
|
|
totalFailed: this.getFailedCount(),
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get count of initialized services
|
|
*/
|
|
private getInitializedCount(): number {
|
|
return Array.from(this.serviceStatuses.values()).filter(
|
|
(s) => s.initialized,
|
|
).length;
|
|
}
|
|
|
|
/**
|
|
* Get count of failed services
|
|
*/
|
|
private getFailedCount(): number {
|
|
return Array.from(this.serviceStatuses.values()).filter((s) => s.error)
|
|
.length;
|
|
}
|
|
|
|
/**
|
|
* Wait for all services to be initialized
|
|
*/
|
|
async waitForInitialization(): Promise<void> {
|
|
if (this.initializationPromise) {
|
|
return this.initializationPromise;
|
|
}
|
|
|
|
this.initializationPromise = new Promise((resolve, reject) => {
|
|
const checkInterval = setInterval(() => {
|
|
const totalServices = this.serviceStatuses.size;
|
|
const initializedCount = this.getInitializedCount();
|
|
const failedCount = this.getFailedCount();
|
|
|
|
logger.debug("[ServiceInit] 🔍 Initialization progress:", {
|
|
totalServices,
|
|
initializedCount,
|
|
failedCount,
|
|
remaining: totalServices - initializedCount - failedCount,
|
|
});
|
|
|
|
if (failedCount > 0) {
|
|
clearInterval(checkInterval);
|
|
const failedServices = Array.from(this.serviceStatuses.values())
|
|
.filter((s) => s.error)
|
|
.map((s) => `${s.name}: ${s.error}`);
|
|
|
|
const error = new Error(
|
|
`Service initialization failed: ${failedServices.join(", ")}`,
|
|
);
|
|
logger.error("[ServiceInit] ❌ Initialization failed:", error);
|
|
reject(error);
|
|
} else if (initializedCount === totalServices) {
|
|
clearInterval(checkInterval);
|
|
logger.info(
|
|
"[ServiceInit] 🎉 All services initialized successfully:",
|
|
{
|
|
totalServices,
|
|
initializedCount,
|
|
},
|
|
);
|
|
resolve();
|
|
}
|
|
}, 100);
|
|
|
|
// Timeout after 30 seconds
|
|
setTimeout(() => {
|
|
clearInterval(checkInterval);
|
|
const error = new Error(
|
|
"Service initialization timeout after 30 seconds",
|
|
);
|
|
logger.error("[ServiceInit] ⏰ Initialization timeout:", error);
|
|
reject(error);
|
|
}, 30000);
|
|
});
|
|
|
|
return this.initializationPromise;
|
|
}
|
|
|
|
/**
|
|
* Get initialization status summary
|
|
*/
|
|
getStatusSummary(): {
|
|
total: number;
|
|
initialized: number;
|
|
failed: number;
|
|
pending: number;
|
|
services: ServiceStatus[];
|
|
} {
|
|
const services = Array.from(this.serviceStatuses.values());
|
|
const total = services.length;
|
|
const initialized = services.filter((s) => s.initialized).length;
|
|
const failed = services.filter((s) => s.error).length;
|
|
const pending = total - initialized - failed;
|
|
|
|
return {
|
|
total,
|
|
initialized,
|
|
failed,
|
|
pending,
|
|
services,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Reset the manager (useful for testing)
|
|
*/
|
|
reset(): void {
|
|
this.serviceStatuses.clear();
|
|
this.initializationPromise = null;
|
|
logger.debug("[ServiceInit] 🔄 Manager reset");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convenience function to get the service initialization manager
|
|
*/
|
|
export const getServiceInitManager = (): ServiceInitializationManager => {
|
|
return ServiceInitializationManager.getInstance();
|
|
};
|
|
|