@mediarithmics/plugins-nodejs-sdk
Version:
This is the mediarithmics nodejs to help plugin developers bootstrapping their plugin without having to deal with most of the plugin boilerplate
153 lines (125 loc) • 4.82 kB
text/typescript
import cluster, { Worker } from 'cluster';
import { Server } from 'http';
import { cpus } from 'os';
import { BasePlugin } from './BasePlugin';
export enum MsgCmd {
LOG_LEVEL_UPDATE_FROM_WORKER,
LOG_LEVEL_UPDATE_FROM_MASTER,
GET_LOG_LEVEL_REQUEST,
}
export interface SocketMsg {
cmd: MsgCmd;
value?: string;
}
export class ProductionPluginRunner {
numCPUs = cpus().length;
pluginPort: number;
plugin: BasePlugin;
server: Server;
constructor(plugin: BasePlugin) {
this.plugin = plugin;
}
updatePluginLogLevel = (value: string) => {
this.plugin.logger.level = value;
};
broadcastLogLevelToWorkers = () => {
const msg = {
cmd: MsgCmd.LOG_LEVEL_UPDATE_FROM_MASTER,
value: this.plugin.logger.level,
};
for (const id in cluster.workers) {
const worker = cluster.workers[id];
if (worker) {
worker.send(msg);
}
}
};
/**
* Socker Listener for master process. It has 4 jobs:
* 1 - If a new token is detected by a Worker, it receives a message and should send a message to each workers
* 2 - If a worker is asking for a token update (ex: the worker was just created because one of his friends died), the master should send a message to each workers
* 3 - If a log level change is detected by a worker, it receives a message and should send a message to each workers
* 4 - If a worker is asking for a log level update (ex: the worker was just created because one of his friends died), the master should send a message to each workers
* @param recMsg
*/
masterListener = (worker: Worker, recMsg: SocketMsg) => {
this.plugin.logger.debug(
`Master ${process.pid} is being called with cmd: ${MsgCmd[recMsg.cmd]}, value: ${
recMsg.value ? recMsg.value : 'undefined'
}`,
);
if (recMsg.cmd === MsgCmd.LOG_LEVEL_UPDATE_FROM_WORKER) {
if (recMsg.value) {
// If we receive a Log Level, we update the token
this.updatePluginLogLevel(recMsg.value);
// We send the log level to each of the workers
this.broadcastLogLevelToWorkers();
} else {
throw new Error(
'We received a LOG_LEVEL_UPDATE_FROM_WORKER msg without logLevel in the value field of the msg.',
);
}
}
};
/**
* Socker Listener of the workers. It should listen for Token update from master and for Log Level changes
* @param recMsg
*/
workerListener = (recMsg: SocketMsg) => {
this.plugin.logger.debug(
`Worker ${process.pid} is being called with cmd: ${MsgCmd[recMsg.cmd]}, value: ${
recMsg.value ? recMsg.value : 'undefined'
}`,
);
if (recMsg.cmd === MsgCmd.LOG_LEVEL_UPDATE_FROM_MASTER) {
if (!recMsg.value) {
throw new Error(
'We received a LOG_LEVEL_UPDATE_FROM_MASTER msg without logLevel in the value field of the msg.',
);
}
const level = recMsg.value.toLocaleLowerCase();
this.plugin.onLogLevelUpdate(level);
this.plugin.logger.debug(`${process.pid}: Updated log level with: ${JSON.stringify(this.plugin.logger.level)}`);
}
};
/**
* Multi threading launch of the App, with socket communicaton to propagate token updates
* @param port
*/
start(port?: number, multiProcessEnabled = false) {
const pluginPort = process.env.PLUGIN_PORT;
this.pluginPort = pluginPort ? parseInt(pluginPort) : 8080;
const serverPort = port ? port : this.pluginPort;
if (multiProcessEnabled) {
if (cluster.isMaster) {
this.plugin.logger.info(`Master ${process.pid} is running`);
// Fork workers.
for (let i = 0; i < this.numCPUs; i++) {
cluster.fork();
}
// Listener for when the Cluster is being called by a worker
cluster.on('message', this.masterListener);
// Sometimes, workers dies
cluster.on('exit', (worker, code, signal) => {
this.plugin.logger.info(`worker ${worker.process.pid as number} died`);
// We add a new worker, with the proper socket listener
cluster.fork();
});
} else {
// We pass the Plugin into MT mode
this.plugin.multiThread = true;
// We attach a socket listener to get messages from master
process.on('message', this.workerListener);
const serverPort = port ? port : this.pluginPort;
this.server = this.plugin.app.listen(serverPort, () =>
this.plugin.logger.info(`${process.pid} Plugin started, listening at ${serverPort}`),
);
this.plugin.logger.info(`Worker ${process.pid} started`);
}
} else {
this.server = this.plugin.app.listen(serverPort, () =>
this.plugin.logger.info(`${process.pid} Plugin started, listening at ${serverPort}`),
);
}
}
}