import logger from "./Logger";
/*
 * Centralized graceful-shutdown coordinator.
 *
 * Before this existed, each subsystem registered its own
 * process.on("SIGTERM" | "SIGINT") handler independently. The telemetry
 * handler in particular called process.exit(0) as soon as the OTEL SDK
 * flushed, which raced every other handler and — crucially — meant the
 * Postgres / Redis / Clickhouse pools were never drained. Connections were
 * left for the OS socket teardown to reap (and leaked outright on SIGKILL or
 * a network partition).
 *
 * Now every subsystem registers an async cleanup callback here, and this class
 * is the single owner of the signal handlers and of process.exit. Handlers run
 * in ascending priority order (lower first) so we stop accepting new work
 * before tearing down the resources that work depends on:
 *
 *   HttpServer (10) -> stop accepting new HTTP requests
 *   Workers    (20) -> stop pulling new queue jobs, finish in-flight jobs
 *   Buffers    (30) -> flush in-memory write buffers to their datastore
 *   DataStores (40) -> drain Postgres / Redis / Clickhouse pools
 *   Telemetry  (50) -> flush traces / metrics / logs / profiles last
 *
 * Handlers in the same tier run concurrently. Each handler is bounded by a
 * per-handler timeout, and the whole sequence by an overall deadline, so a
 * single hung handler can never wedge the shutdown.
 */
export var ShutdownPriority;
(function (ShutdownPriority) {
    ShutdownPriority[ShutdownPriority["HttpServer"] = 10] = "HttpServer";
    ShutdownPriority[ShutdownPriority["Workers"] = 20] = "Workers";
    ShutdownPriority[ShutdownPriority["Buffers"] = 30] = "Buffers";
    ShutdownPriority[ShutdownPriority["DataStores"] = 40] = "DataStores";
    ShutdownPriority[ShutdownPriority["Telemetry"] = 50] = "Telemetry";
})(ShutdownPriority || (ShutdownPriority = {}));
class GracefulShutdown {
    /*
     * Register a cleanup callback to run on SIGTERM / SIGINT. Registering by a
     * stable name is idempotent: a repeat registration (e.g. a second connect())
     * replaces the previous callback instead of stacking a duplicate. Callers
     * that own multiple independent resources (e.g. two Clickhouse pools) must
     * therefore pass distinct names.
     */
    static registerHandler(name, priority, callback) {
        const existingIndex = this.handlers.findIndex((handler) => {
            return handler.name === name;
        });
        if (existingIndex >= 0) {
            this.handlers[existingIndex] = { name, priority, callback };
        }
        else {
            this.handlers.push({ name, priority, callback });
        }
        this.installSignalListeners();
    }
    static installSignalListeners() {
        if (this.signalListenersInstalled) {
            return;
        }
        this.signalListenersInstalled = true;
        process.on("SIGTERM", () => {
            void this.shutdown("SIGTERM");
        });
        process.on("SIGINT", () => {
            void this.shutdown("SIGINT");
        });
    }
    static async shutdown(signal) {
        if (this.isShuttingDown) {
            /*
             * A second signal while we're already draining means the operator (or
             * orchestrator) is impatient. Bail out immediately.
             */
            logger.warn(`GracefulShutdown: received ${signal} while already shutting down. Forcing exit.`);
            return process.exit(1);
        }
        this.isShuttingDown = true;
        logger.info(`GracefulShutdown: received ${signal}. Draining ${this.handlers.length} handler(s)...`);
        const forceExitTimer = setTimeout(() => {
            logger.error(`GracefulShutdown: exceeded ${this.overallTimeoutMs}ms overall deadline. Forcing exit.`);
            return process.exit(1);
        }, this.overallTimeoutMs);
        // Don't let this timer keep the event loop alive on its own.
        forceExitTimer.unref();
        // Run handlers tier by tier; lower priority tiers complete before the next.
        const tiers = Array.from(new Set(this.handlers.map((handler) => {
            return handler.priority;
        }))).sort((a, b) => {
            return a - b;
        });
        for (const tier of tiers) {
            const handlersInTier = this.handlers.filter((handler) => {
                return handler.priority === tier;
            });
            // Handlers within a tier are independent, so run them concurrently.
            await Promise.all(handlersInTier.map((handler) => {
                return this.runHandlerWithTimeout(handler);
            }));
        }
        clearTimeout(forceExitTimer);
        logger.info("GracefulShutdown: all handlers complete. Exiting cleanly.");
        return process.exit(0);
    }
    static async runHandlerWithTimeout(handler) {
        logger.debug(`GracefulShutdown: running handler "${handler.name}"...`);
        let timer = null;
        const timeout = new Promise((resolve) => {
            timer = setTimeout(() => {
                logger.warn(`GracefulShutdown: handler "${handler.name}" exceeded ${this.perHandlerTimeoutMs}ms. Moving on.`);
                return resolve();
            }, this.perHandlerTimeoutMs);
            timer.unref();
        });
        const run = (async () => {
            try {
                await handler.callback();
                logger.debug(`GracefulShutdown: handler "${handler.name}" done.`);
            }
            catch (err) {
                logger.error(`GracefulShutdown: handler "${handler.name}" failed:`);
                logger.error(err);
            }
        })();
        await Promise.race([run, timeout]);
        if (timer) {
            clearTimeout(timer);
        }
    }
}
GracefulShutdown.handlers = [];
GracefulShutdown.signalListenersInstalled = false;
GracefulShutdown.isShuttingDown = false;
/*
 * How long a single handler may run before we give up on it and move on.
 * Kept comfortably under the orchestrator (Kubernetes) default
 * terminationGracePeriodSeconds of 30s.
 */
GracefulShutdown.perHandlerTimeoutMs = 10000;
/*
 * Hard ceiling for the entire shutdown. If we blow past this we force-exit
 * rather than risk being SIGKILLed mid-cleanup.
 */
GracefulShutdown.overallTimeoutMs = 25000;
export default GracefulShutdown;
//# sourceMappingURL=GracefulShutdown.js.map