UNPKG

@imqueue/core

Version:

Simple JSON-based messaging queue for inter service communication

215 lines 7.97 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.UDPClusterManager = exports.DEFAULT_UDP_CLUSTER_MANAGER_OPTIONS = void 0; const ClusterManager_1 = require("./ClusterManager"); const dgram_1 = require("dgram"); const os_1 = require("os"); var MessageType; (function (MessageType) { MessageType["Up"] = "up"; MessageType["Down"] = "down"; })(MessageType || (MessageType = {})); exports.DEFAULT_UDP_CLUSTER_MANAGER_OPTIONS = { broadcastPort: 63000, broadcastAddress: '255.255.255.255', aliveTimeoutCorrection: 1000, }; const LOCALHOST_ADDRESSES = [ 'localhost', '127.0.0.1', '::1', ]; /** * UDP broadcast-based cluster management implementation * * @example * ~~~typescript * const queue = new ClusteredRedisQueue('ClusteredQueue', { * clusterManagers: [new UDPBroadcastClusterManager()], * }); * ~~~ */ class UDPClusterManager extends ClusterManager_1.ClusterManager { get socket() { return UDPClusterManager.sockets[this.socketKey]; } set socket(socket) { UDPClusterManager.sockets[this.socketKey] = socket; } constructor(options) { super(); this.options = Object.assign(Object.assign({}, exports.DEFAULT_UDP_CLUSTER_MANAGER_OPTIONS), options || {}); this.startListening(this.options); process.on('SIGTERM', UDPClusterManager.free); process.on('SIGINT', UDPClusterManager.free); process.on('SIGABRT', UDPClusterManager.free); } static async free() { const socketKeys = Object.keys(UDPClusterManager.sockets); await Promise.all(socketKeys.map(socketKey => UDPClusterManager.destroySocket(socketKey, UDPClusterManager.sockets[socketKey]))); } listenBroadcastedMessages(listener, options) { const address = UDPClusterManager.selectNetworkInterface(options); this.socketKey = `${address}:${options.broadcastPort}`; if (!this.socket) { this.socket = (0, dgram_1.createSocket)({ type: 'udp4', reuseAddr: true }); this.socket.bind(options.broadcastPort, address); } this.socket.on('message', message => listener(UDPClusterManager.parseBroadcastedMessage(message))); } startListening(options = {}) { this.listenBroadcastedMessages(UDPClusterManager.processBroadcastedMessage(this), options); } static verifyHosts(host, hosts) { const normalizedHosts = hosts === 'localhost' ? LOCALHOST_ADDRESSES : hosts; return normalizedHosts.includes(host); } static processMessageOnCluster(cluster, message, aliveTimeoutCorrection) { const server = cluster.find(message); if (server && message.type === MessageType.Down) { clearTimeout(server.timer); return cluster.remove(message); } if (!server && message.type === MessageType.Up) { cluster.add(message); const added = cluster.find(message, true); if (added) { UDPClusterManager.serverAliveWait(cluster, added, aliveTimeoutCorrection); } return; } if (server && message.type === MessageType.Up) { return UDPClusterManager.serverAliveWait(cluster, server, aliveTimeoutCorrection, message); } } static processBroadcastedMessage(context) { return message => { if (context.options.excludeHosts && UDPClusterManager.verifyHosts(message.host, context.options.excludeHosts)) { return; } if (context.options.includeHosts && !UDPClusterManager.verifyHosts(message.host, context.options.includeHosts)) { return; } context.anyCluster(cluster => { UDPClusterManager.processMessageOnCluster(cluster, message, context.options.aliveTimeoutCorrection); }).then(); }; } static parseBroadcastedMessage(input) { const [name, id, type, address = '', timeout = '0',] = input.toString().split('\t'); const [host, port] = address.split(':'); return { id, name, type: type.toLowerCase(), host, port: parseInt(port), timeout: parseFloat(timeout) * 1000, }; } static serverAliveWait(cluster, server, aliveTimeoutCorrection, message) { if (server.timer) { clearTimeout(server.timer); server.timer = undefined; } server.timestamp = Date.now(); if (message) { server.timeout = message.timeout; } const correction = aliveTimeoutCorrection || 0; const timeout = (server.timeout || 0) + correction; if (timeout <= 0) { return; } const timerId = setTimeout(() => { const existing = cluster.find(server, true); if (!existing || existing.timer !== timerId) { return; } const now = Date.now(); if (!existing.timestamp) { clearTimeout(existing.timer); existing.timer = undefined; cluster.remove(existing); return; } const delta = now - existing.timestamp; const currentTimeout = (existing.timeout || 0) + correction; if (delta >= currentTimeout) { clearTimeout(existing.timer); existing.timer = undefined; cluster.remove(existing); } }, timeout); server.timer = timerId; } /** * Destroys the UDPClusterManager by closing all opened network connections * and safely destroying all blocking sockets * * @returns {Promise<void>} * @throws {Error} */ async destroy() { await UDPClusterManager.destroySocket(this.socketKey, this.socket); } static async destroySocket(socketKey, socket) { if (!socket) { return; } return await new Promise((resolve, reject) => { try { if (typeof socket.close === 'function') { socket.removeAllListeners(); socket.close(() => { socket === null || socket === void 0 ? void 0 : socket.unref(); if (socketKey && UDPClusterManager.sockets[socketKey]) { delete UDPClusterManager.sockets[socketKey]; } resolve(); }); return; } resolve(); } catch (e) { reject(e); } }); } static selectNetworkInterface(options) { const interfaces = (0, os_1.networkInterfaces)(); const limitedBroadcastAddress = options.limitedBroadcastAddress; const broadcastAddress = options.broadcastAddress || limitedBroadcastAddress; const defaultAddress = '0.0.0.0'; if (!broadcastAddress) { return defaultAddress; } const equalAddresses = broadcastAddress === limitedBroadcastAddress; if (equalAddresses) { return defaultAddress; } for (const key in interfaces) { if (!interfaces[key]) { continue; } for (const net of interfaces[key]) { const shouldBeSelected = net.family === 'IPv4' && net.address.startsWith(broadcastAddress.replace(/\.255/g, '')); if (shouldBeSelected) { return net.address; } } } return defaultAddress; } } exports.UDPClusterManager = UDPClusterManager; UDPClusterManager.sockets = {}; //# sourceMappingURL=UDPClusterManager.js.map