UNPKG

@mdf.js/service-registry

Version:

MMS - API - Service Registry

189 lines 8.52 kB
"use strict"; /** * Copyright 2024 Mytra Control S.L. All rights reserved. * * Use of this source code is governed by an MIT-style license that can be found in the LICENSE file * or at https://opensource.org/licenses/MIT. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MasterPort = void 0; const tslib_1 = require("tslib"); const core_1 = require("@mdf.js/core"); const cluster_1 = tslib_1.__importDefault(require("cluster")); const types_1 = require("../types"); const Port_1 = require("./Port"); /** * The MasterPort class facilitates health monitoring within a master process of a clustered Node.js * application. * It periodically requests health checks from worker processes and aggregates their responses to * maintain an updated view of the application's overall health status. * * Inherits from the Port class, leveraging shared functionality and adding specific mechanisms for * handling health diagnostic requests and responses in a cluster master context. */ class MasterPort extends Port_1.Port { /** * Create an instance of health manager in a master process * @param aggregator - Aggregator instance for aggregating health checks from workers. * @param logger - Logger instance for logging activities * @param interval - interval in milliseconds between each health registry poll from workers. */ constructor(aggregator, logger, interval = types_1.DEFAULT_CONFIG_HEALTH_CLUSTER_UPDATE_INTERVAL) { super(logger); this.aggregator = aggregator; this.interval = interval; /** Request sequence number */ this.requestId = 0; /** * Sends a request to all worker processes to send their current health registries. * Handles responses, timeouts, and updates the aggregator with aggregated health checks from * workers. */ this.onSendRequest = () => { this.requestId = this.requestId + 1; if (!Number.isFinite(this.requestId)) { this.requestId = 0; } let pendingResponses = Object.keys(this.workers).length; let timeOutHandler; let updatedChecks = this.initializeWorkersChecks(); const onWorkerResponse = (worker, message) => { if (message.requestId !== this.requestId) { // Stryker disable next-line all this.logger.debug(`Health response from worker [${worker.process.pid}] out of the valid period`); return; } if (message.type === types_1.HealthMessageType.RES) { pendingResponses = pendingResponses - 1; updatedChecks = this.mergeChecks(updatedChecks, worker, message.checks); if (pendingResponses === 0 && timeOutHandler) { onFinished(); } } }; const onTimeOut = () => { // Stryker disable next-line all this.logger.debug(`Timeout waiting for health response from workers - ${pendingResponses} pending`); onFinished(); }; const onFinished = () => { // Stryker disable next-line all this.logger.debug('Health update finished'); cluster_1.default.off('message', onWorkerResponse); if (timeOutHandler) { clearTimeout(timeOutHandler); timeOutHandler = undefined; } this.aggregator.updateWorkersChecks(updatedChecks); }; timeOutHandler = setTimeout(onTimeOut, this.interval * 0.9); cluster_1.default.on('message', onWorkerResponse); for (const worker of Object.values(this.workers)) { if (worker === null || worker === void 0 ? void 0 : worker.isConnected()) { this.logger.debug(`Sending an health update request to worker [${worker.process.pid}]`); worker.send({ type: types_1.HealthMessageType.REQ, requestId: this.requestId, }); } } }; // Stryker disable next-line all this.logger.debug(`New master port instance created: ${JSON.stringify({ interval })}`); } /** * Starts the process of periodically polling health registries from worker processes. * Ensures that only one polling mechanism is active at any given time. */ start() { // Stryker disable next-line all this.logger.debug('Starting health registry polling in master process'); this.aggregator.updateWorkersChecks(this.initializeWorkersChecks()); if (!this.timeInterval) { this.onSendRequest(); this.timeInterval = setInterval(this.onSendRequest, this.interval); } } /** * Stops the polling of health registries from worker processes and clears the polling interval. */ stop() { // Stryker disable next-line all this.logger.debug('Stopping health registry polling in master process'); if (this.timeInterval) { clearInterval(this.timeInterval); this.timeInterval = undefined; } } /** * Initializes health checks for all workers, setting up baseline checks for connection status * and default health status as 'outdated' until updated by worker responses. * @returns Initial checks for all workers. */ initializeWorkersChecks() { const checks = { [types_1.SYSTEM_WORKER]: [], [types_1.SYSTEM_WORKER_HEALTH]: [] }; for (const worker of Object.values(this.workers)) { if (worker) { checks[types_1.SYSTEM_WORKER].push({ componentId: `${worker.process.pid}`, componentType: 'process', status: worker.isConnected() ? core_1.Health.STATUS.PASS : core_1.Health.STATUS.FAIL, observedValue: worker.isConnected() ? types_1.WORKER_CONNECTION_STATE.CONNECTED : types_1.WORKER_CONNECTION_STATE.DISCONNECTED, observedUnit: 'status', workerId: worker.id, workerPid: worker.process.pid, time: new Date().toISOString(), }); checks[types_1.SYSTEM_WORKER_HEALTH].push({ componentId: `${worker.process.pid}`, componentType: 'process', status: core_1.Health.STATUS.FAIL, observedValue: types_1.WORKER_STATUS.OUTDATED, observedUnit: 'status', workerId: worker.id, workerPid: worker.process.pid, time: new Date().toISOString(), }); } } return checks; } /** * Merge the checks from a worker, including in them the PID and worker identifier, with passed * feed passed as argument * @param feed - feed where to merge the checks * @param worker - Worker that emit the checks * @param checks - Checks to be merged * @returns The feed with the merged checks */ mergeChecks(feed, worker, checks) { for (const [key, value] of Object.entries(checks)) { const entry = key; const entryChecks = value.map(check => ({ ...check, workerId: worker.id, workerPid: worker.process.pid, })); feed[entry] = feed[entry] ? feed[entry].concat(entryChecks) : entryChecks; } if (feed[types_1.SYSTEM_WORKER_HEALTH]) { const result = feed[types_1.SYSTEM_WORKER_HEALTH].find(workerEntry => workerEntry['workerId'] === worker.id); if (result) { result['observedValue'] = types_1.WORKER_STATUS.UPDATED; result['status'] = core_1.Health.STATUS.PASS; } } return feed; } /** * Retrieves a dictionary of currently active worker processes. * @returns A dictionary of Worker instances indexed by their cluster worker ID. */ get workers() { return cluster_1.default.workers ? cluster_1.default.workers : {}; } } exports.MasterPort = MasterPort; //# sourceMappingURL=MasterPort.js.map