NodeJS Cluster how share object array across workers - node.js

So I have setup a simple nodejs cluster game, I am new to nodejs. basically players connect to to my worker using socket.io then they get created to a Player Object then added to my PlayerManager.LIST array. Now this causes me some issues as the PlayerManager.LIST is on each of workers and are not sync'd.
So my question is, is there a better way of doing this so that if I connect to worker 2 I see same player list as worker 1's.
Structure at the moment:
app.js
-> worker
->-> PlayerManager (Contains List)
->->-> Player
Git Repo: https://github.com/mrhid6/game_app_v2

NodeJS Clusters are based on Nodejs Child Processes. In child processes you can send data between parent (Master in cluster) and child (worker in cluster) via messages over IPC channel. You can do the same with clusters using message events
var cluster = require('cluster');
var _ = require('lodash');
var http = require('http');
var workers = [];
var workerCount = 4;
if (cluster.isMaster) {
for (var i = 0; i < workerCount; i++) {
var worker = cluster.fork();
worker.on('message', function(msg) {
if (msg.task === 'sync') {
syncPlayerList(msg.data);
}
});
}
workers.push[worker];
} else {
var worker = new Worker();
process.on('message', function(msg) {
if (msg.task === 'sync') {
worker.playerList = msg.data;
}
});
}
function syncPlayerList (playerList) {
_.forEach(workers, function (worker) {
worker.send({
task: 'sync',
data: playerList
});
});
};
// worker class
function Worker() {
this.playerList = [];
}
Worker.prototype.sendSyncEvent = function () {
process.send({
task: 'sync',
data: this.playerList
})
};

Related

Nodejs cluster unable to kill worker running infinite loop

It is possible to kill a cluster worker that is running an infinite loop? I've tried, but unable to kill the worker. I guess the kill command cannot get onto the worker's js event loop. Any ideas on how else I can do this? When the master receives a "start" message, I want it to fork a worker. When the master receives a "stop" message, I want it to kill the worker.
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
const process = require('process');
const { nrpin, nrpout } = require("./helpers/PubSub");
const chalk = require('chalk');
//https://leanpub.com/thenodejsclustermodule/read
//https://gist.github.com/jpoehls/2232358
const arrWorkers = [];
if (cluster.isMaster) {
masterProcess();
} else {
childProcess();
}
function masterProcess() {
console.log(chalk.blueBright(`Master ${process.pid} is running`));
nrpin.on("start", async (bot) => {
console.log("Start:", bot._id);
if (arrWorkers.length == numCPUs){
console.log(chalk.yellowBright("No CPUs available to create worker!"));
}
const worker = cluster.fork();
arrWorkers.push({
workerId: worker.id,
botId: bot._id
})
})
nrpin.on("stop", async (bot) => {
console.log("Stop:", bot._id);
const worker = arrWorkers.find(x => x.botId == bot._id);
if (worker){
console.log("killing worker:", worker.workerId);
cluster.workers[worker.workerId].kill();
}
})
// Be notified when workers die
cluster.on('exit', function(worker, code, signal) {
if (worker.isDead()) {
console.info(`${chalk.redBright('worker dead pid')}: ${worker.process.pid}`);
}
});
}
function childProcess() {
console.log(chalk.green(`Worker ${process.pid} started...`));
while(true){
console.log(Date.now());
}
}
Never mind, I solved this using process.kill
let process_id = cluster.workers[worker.workerId].process.pid;
process.kill(process_id);

Sharing object between child and fork process node js

I'm working on child process using fork. but got totally confused on few things
• will it (process)pass app object instance eg:- let app = express(); using IPC
I m trying to explain my senario, first I have server.js where I initialize (starting point) server and other file is my task.js from where I am doing heavy task like reading a big file data and sending data back to other server. For send I had require authorization from that server whose logic is present in main.js and if any error occur I'm send email with few detail to client. Below provide code for email and authorization in main.js
Let task = require('./task.js')
app.sendEmail = function (message, emailinfo, attachment){
// my email logic
}
app.auth= function(host,port)
// Authorization logic
}
New task(app).run()
In task.js (sample code)
Class Task {
constructor(app){
this.app =app
}
run(){
fs.readfile('myfile',function(err,data){
if(err){ let msg =err;
let clientinf; clientinf.to = "client email";
clientinf.cc = " other user in CC";
this.app.sendEmail(msg, clientinf, attach);
}else{
let host='other server url';
let port='port';
this.app.auth(host,port);
}
})
}
}
I want to run task.js in one more thread . note cluster and worker(because I m using node 10.19 so not confident that worker works properly) I don't want to use . It is possible to use folk or spawn to share data between each other. If not how I can achieve my requirement using thread?
Here are two solutions. The first is using the Worker class from the worker_threads module but since you don't want to update the node version the second solution is using fork function from child_process module. They do pretty much the same thing to be honest I can't tell which is better but the worker_threads solution is more recent.
Solution 1:
const { Worker } = require('worker_threads')
const task_script = path.join(__dirname, "./task.js")
const obj = {data:"data"}
const worker = new Worker(task_script, {
workerData: JSON.stringify(obj)
})
worker.on("error", (err) => console.log(err))
worker.on("exit", () => console.log("exit"))
worker.on("message", (data) => {
console.log(data)
res.send(data)
})
and you have to change the task.js code slightly.Here it is
const { parentPort, workerData, isMainThread } = require('worker_threads')
class Task {
constructor(app){
this.app = app
}
run(){
if (!isMainThread) {
console.log("workerData: ", workerData) //you have worker data here
fs.readfile('myfile',function(err,data){
if(err){ let msg = err;
let clientinf; clientinf.to = "client email";
clientinf.cc = " other user in CC";
this.app.sendEmail(msg, clientinf, attach);
parentPort.postMessage(msg) //use can send message to parent like this
} else {
let host='other server url';
let port='port';
this.app.auth(host,port);
}
})
}
}
}
And here is the second solution
const { fork } = require('child_process');
const forked = fork('task.js');
forked.on('message', (msg) => {
console.log('Message from child', msg);
});
forked.send({ hello: 'world' });
and the taks.js way of sending and recieving data with this method
class Task {
constructor(app){
this.app = app
}
run(){
//receive
process.on('message', (msg) => {
console.log('Message from parent:', msg);
});
fs.readfile('myfile',function(err,data){
if(err){ let msg = err;
let clientinf; clientinf.to = "client email";
clientinf.cc = " other user in CC";
this.app.sendEmail(msg, clientinf, attach);
process.send(msg); //send method
} else {
let host='other server url';
let port='port';
this.app.auth(host,port);
}
})
}
}

Worker stopped sending data to master

I have an app (master) which distributes work to n amount of workers. Inside the worker js I have hooked the console output as follows:
console._log = console.log;
console._error = console.error;
console.log = (...args) => {
process.send({
cmd:'log',
channel:'out',
data: args.join(' ')
});
};
console.error = (...args) => {
process.send({
cmd:'log',
channel:'err',
data: args.join(' ')
});
};
The master now is responsible of logging all incoming messages into a file besides std. Accomplished with the following code & module:
const intercept = require('intercept-stdout');
const stripAnsi = require('strip-ansi');
const unhook_intercept = intercept(function (str) {
// stdout
fs.appendFileSync(lib.logOutFile(), stripAnsi(str));
}, function (str) {
// stderr
fs.appendFileSync(lib.logErrFile(), stripAnsi(str));
});
I have noticed in the logs that a worker after 1,5 day stopped sending messages. In the master I have worker exit detection:
cluster.on('exit', (worker, code, signal) => {
if (signal) {
console.log(`${lib.dateTimeStamp()} - ${chalk.magenta('[')}${chalk.cyan(worker.process.pid)}${chalk.magenta(']')}\tWorker: ${chalk.yellow(`was killed by signal: ${signal}`)}`);
} else if (code !== 0) {
console.error(`${lib.dateTimeStamp()} - ${chalk.magenta('[')}${chalk.cyan(worker.process.pid)}${chalk.magenta(']')}\tWorker: ${chalk.red(`exited with error code: ${code}`)}`);
let newWorker = cluster.fork();
let data = work[worker.process.pid];
let d = new Date();
status[worker.process.pid].status = 'dead';
status[newWorker.process.pid] = {
started: `${d.toLocaleDateString()} ${d.toLocaleTimeString()}`,
status: 'alive'
};
delete work[worker.process.pid];
work[newWorker.process.pid] = data;
newWorker.send({
options: cfg.options,
websites: work[newWorker.process.pid]
});
} else {
delete work[worker.process.pid];
delete status[worker.process.pid]
console.log(`${lib.dateTimeStamp()} - ${chalk.magenta('[')}${chalk.cyan(worker.process.pid)}${chalk.magenta(']')}\tWorker: ${chalk.green('exited successfully')}`);
}
});
Exit was not triggered as I have seen in the logs. At the moment I have only assumptions and I'd like your opinions. Could it be because:
The synchronous file logging.
A worker disconnected on its own.
A worker exited and the exit event was missed.
Your opinion...

Prevent multiple console logging output while clustering

I'm using the cluster module for nodejs.
Here is how I have it set up:
var cluster = require('cluster');
if (cluster.isMaster) {
var numCPUs = require('os').cpus().length;
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
}else{
console.log("Turkey Test");
}
Now, I am forking 6 threads (6 cores) on my PC. So, when debugging my app and reading data from the console, this will appear:
Is there anyway to make console.log output only once regardless of how many clusters are running?
You could use the fact that you can communicate with the workers, and send a message that tells each worker if it should log or not. You'd send it so that only one worker (The first one for example) should log:
var cluster = require('cluster');
if (cluster.isMaster) {
var numCPUs = require('os').cpus().length;
for (var i = 0; i < numCPUs; i++) {
cluster.fork().send({doLog: i == 0});
}
}else{
process.on('message', function(msg) {
if(msg.doLog) {
console.log('Turkey Test');
}
});
}

ENOTSUP errnoException in NodeJS using mssql in cluster worker

I am trying to connect to an SQL Server database within a cluster worker. I am using node-mssql and Tedious as the driver. Here is my test code.
'use strict';
var os = require('os');
var numCPUs = os.cpus().length;
var cluster = require('cluster');
var mssql = (cluster.isMaster == false ? require('mssql') : null);
function workerLog(msg) {
process.send({msg:"CLUSTER-NODE-"+cluster.worker.id+": "+msg});
}
if(cluster.isMaster == true) {
console.log("MASTER: "+"SPAWNING "+numCPUs+" CLUSTER NODES");
for(var i = 0; i < numCPUs; i++) {
var worker = cluster.fork();
worker.on('message', function(msg) {
console.log(msg.msg);
});
}
var timeOuts = [];
var workerError = function(workerId) {
console.log("MASTER: "+"AN EXECUTION ISSUE OCCURRED WITH CLUSTER NODE "+workerId);
};
cluster.on('fork', function(worker) {
timeOuts[worker.id] = setTimeout(workerError,10000,worker.id);
console.log("MASTER: "+"SPAWNED CLUSTER NODE "+worker.id);
});
cluster.on('online', function(worker) {
console.log("MASTER: "+"CLUSTER NODE "+worker.id+" COMING ONLINE");
});
cluster.on('listening', function(worker,address) {
clearTimeout(timeOuts[worker.id]);
console.log("MASTER: "+"CLUSTER NODE "+worker.id+" IS LISTENING");
});
cluster.on('disconnect', function(worker) {
console.log("MASTER: "+"CLUSTER NODE "+worker.id+" HAS DISCONNECTED");
});
cluster.on('exit', function(worker,code,signal) {
clearTimeout(timeOuts[worker.id]);
console.log("MASTER: "+"CLUSTER NODE "+worker.id+" HAS EXITED");
});
} else {
var dbName = "Prefs";
var cfg = {
driver: 'tedious',
user: 'webmi',
password: 'webmi01',
server: "localhost\\SQLEXPRESS",
database: dbName,
options: {
useColumnNames: true,
isolationLevel: 1, // READ_UNCOMMITTED
connectionIsolationLevel: 1 // READ_UNCOMMITTED
}
};
var dbConn = new mssql.Connection(cfg);
workerLog("CONNECT TO "+dbName);
dbConn.connect().then(function() {
if(dbConn.connected) {
workerLog("CONNECTION TO "+dbName+" EXISTS");
} else {
workerLog("NOT CONNECTED TO "+dbName+" BUT NO ERROR DETECTED");
}
}).catch(function(error) {
workerLog("CANNOT CONNECT TO DATABASE\n"+error.stack);
});
}
And here is what I get when I run it: node test.js
D:\proj\CTech\9.2\Bin\Node>node test.js
MASTER: SPAWNING 2 CLUSTER NODES
MASTER: SPAWNED CLUSTER NODE 1
MASTER: SPAWNED CLUSTER NODE 2
MASTER: CLUSTER NODE 1 COMING ONLINE
MASTER: CLUSTER NODE 2 COMING ONLINE
CLUSTER-NODE-2: CONNECT TO Prefs
CLUSTER-NODE-1: CONNECT TO Prefs
events.js:85
throw er; // Unhandled 'error' event
^
Error: write ENOTSUP
at exports._errnoException (util.js:746:11)
at ChildProcess.target._send (child_process.js:484:28)
at ChildProcess.target.send (child_process.js:416:12)
at sendHelper (cluster.js:676:8)
at send (cluster.js:512:5)
at cluster.js:488:7
at SharedHandle.add (cluster.js:99:3)
at queryServer (cluster.js:480:12)
at Worker.onmessage (cluster.js:438:7)
at ChildProcess.<anonymous> (cluster.js:692:8)
D:\proj\CTech\9.2\Bin\Node>
Looking at child_process.js, It appears that the worker is trying to send the connection handle back to the master (for sharing purposes?) but I may be wrong on that. In any case, the worker faults on the send attempt.
Is there a way to prevent this fault? Or is there a way to have the worker NOT attempt to share this handle with the master?

Resources