@imqueue/core
Version:
Simple JSON-based messaging queue for inter service communication
215 lines • 7.97 kB
JavaScript
;
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