timesafari
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

/**
* 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();
};