node-cluster-ipc
Version:
A lightweight for Inter-Process Communication (IPC) between master and worker processes using the cluster module
130 lines (129 loc) • 4.66 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClusterIpc = void 0;
const cluster_1 = __importDefault(require("cluster"));
const crypto_1 = require("crypto");
const events_1 = require("events");
class ClusterIpc extends events_1.EventEmitter {
constructor(options = { requestTimeout: 5000 }) {
super();
this.options = options;
this.workerIndex = 0;
this.workerMap = new Map();
this.pendingRequests = new Map();
this.isPrimary ? this.setupPrimary() : this.setupWorker();
}
get isPrimary() {
return cluster_1.default.isPrimary || cluster_1.default.isMaster;
}
get isWorker() {
return cluster_1.default.isWorker;
}
get worker() {
return cluster_1.default.worker;
}
get workers() {
return cluster_1.default.workers;
}
send(channel, data, workerId) {
const message = { channel, data };
const worker = this.isPrimary ? this.getWorker(workerId) : this.worker;
if (worker)
worker.send(message);
}
publish(channel, data) {
if (!this.isPrimary) {
throw new Error('Method "publish" can only be called from the primary process');
}
const message = { channel, data };
const workers = Object.values(this.workers || {});
if (workers.length) {
workers.forEach(worker => worker.send(message));
}
else {
throw new Error('No workers available');
}
}
async request(channel, data, workerId) {
const requestId = (0, crypto_1.randomUUID)();
const message = { channel, data, requestId };
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
const request = this.pendingRequests.get(requestId);
if (request) {
this.pendingRequests.delete(requestId);
reject(new Error('Request timeout'));
}
}, this.options.requestTimeout);
this.pendingRequests.set(requestId, { resolve, reject, timeout });
try {
const worker = this.isPrimary ? this.getWorker(workerId) : this.worker;
if (worker)
worker.send(message);
}
catch (error) {
clearTimeout(timeout);
this.pendingRequests.delete(requestId);
reject(error);
}
});
}
reply(channel, data, requestId, workerId) {
const message = { channel, data, requestId, isReply: true };
const worker = this.isPrimary ? this.getWorker(workerId) : this.worker;
if (worker)
worker.send(message);
}
setupPrimary() {
cluster_1.default.on('online', (worker) => {
worker.on('message', (msg) => this.handleMessage(msg, worker));
});
}
setupWorker() {
const worker = this.worker;
worker.on('message', (msg) => this.handleMessage(msg, worker));
}
handleMessage(msg, worker) {
if (msg.isReply && msg.requestId) {
const request = this.pendingRequests.get(msg.requestId);
if (request) {
clearTimeout(request.timeout);
this.pendingRequests.delete(msg.requestId);
request.resolve(msg.data);
}
}
else if (msg.requestId) {
this.emit('request', msg.channel, msg.data, (response) => {
this.reply(msg.channel, response, msg.requestId, worker.id);
});
}
else {
this.emit('message', msg.channel, msg.data);
}
}
getWorker(id) {
const workers = Object.values(this.workers || {});
if (workers.length === 0) {
throw new Error('No workers available');
}
if (id !== undefined) {
const worker = this.workers[id];
if (worker)
return worker;
const workerId = this.workerMap.get(String(id));
if (workerId !== undefined) {
const worker = this.workers[workerId];
if (worker)
return worker;
}
}
const worker = workers[this.workerIndex];
this.workerIndex = (this.workerIndex + 1) % workers.length;
this.workerMap.set(String(id), worker.id);
return worker;
}
}
exports.ClusterIpc = ClusterIpc;