import { log } from "./log"; const shutdownHandlers = new Map Promise>>(); const shutdownController = new AbortController(); export const shutdownSignal = shutdownController.signal; export function onShutdown(name: string, callback: () => Promise): () => void { if (shutdownSignal.aborted) { // If already shutting down, execute immediately callback(); return () => {}; } if (!shutdownHandlers.has(name)) { shutdownHandlers.set(name, []); } const handlers = shutdownHandlers.get(name)!; handlers.push(callback); // Return unsubscribe function return () => { const index = handlers.indexOf(callback); if (index !== -1) { handlers.splice(index, 1); if (handlers.length === 0) { shutdownHandlers.delete(name); } } }; } export function isShutdown() { return shutdownSignal.aborted; } export async function awaitShutdown() { await new Promise((resolve) => { process.on('SIGINT', async () => { log('Received SIGINT signal. Exiting...'); resolve(); }); process.on('SIGTERM', async () => { log('Received SIGTERM signal. Exiting...'); resolve(); }); }); shutdownController.abort(); // Copy handlers to avoid race conditions const handlersSnapshot = new Map Promise>>(); for (const [name, handlers] of shutdownHandlers) { handlersSnapshot.set(name, [...handlers]); } // Execute all shutdown handlers concurrently const allHandlers: Promise[] = []; let totalHandlers = 0; for (const [name, handlers] of handlersSnapshot) { totalHandlers += handlers.length; log(`Starting ${handlers.length} shutdown handlers for: ${name}`); handlers.forEach((handler, index) => { const handlerPromise = handler().then( () => {}, (error) => log(`Error in shutdown handler ${name}[${index}]:`, error) ); allHandlers.push(handlerPromise); }); } if (totalHandlers > 0) { log(`Waiting for ${totalHandlers} shutdown handlers to complete...`); const startTime = Date.now(); await Promise.all(allHandlers); const duration = Date.now() - startTime; log(`All ${totalHandlers} shutdown handlers completed in ${duration}ms`); } } export async function keepAlive(name: string, callback: () => Promise): Promise { let completed = false; let result: T; let error: any; const promise = new Promise((resolve) => { const unsubscribe = onShutdown(`keepAlive:${name}`, async () => { if (!completed) { log(`Waiting for keepAlive operation to complete: ${name}`); await promise; } }); // Run the callback callback().then( (res) => { result = res; completed = true; unsubscribe(); resolve(); }, (err) => { error = err; completed = true; unsubscribe(); resolve(); } ); }); // Wait for completion await promise; if (error) { throw error; } return result!; }