How do you set up workers in a cluster using Node.js without having it in the same file?

Issue

I have this code in an index.js that I mostly left unmodified from the official Node.js documentation.

const https = require('https');
const fs = require('fs');
const cluster = require('node:cluster');
const numCPUs = require('node:os').cpus().length;
const process = require('node:process');

const livegame = require('./server-livegame');
const matchmaking = require('./server-matchmaking');

//Start ExpressJS
var express = require('express');
const { match } = require('assert');

var app = express();
app.use(express.json());


if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);

  for (let i = 0; i< numCPUs; i++){
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died`);
  });
}
else
{
  new livegame;
  new matchmaking;
}

Here is a simplified code for livegame/matchmaking that produces the error.

const https = require('https');
const fs = require('fs');
const mongoose = require('mongoose');

//Import Models
const LiveMatch = require('./models/livematch');

//Start ExpressJS
var express = require('express');
const { match } = require('assert');

//Interface Security
var options = {
  key: fs.readFileSync('key.pem', 'utf8'),
  cert: fs.readFileSync('cert.pem', 'utf8')
};

//Server
var httpsServer = https.createServer(options, app);

httpsServer.listen(443);
var app = express();
app.use(express.json());

const chat = 
    {
        puuid: String,
        name: String,
        roleID: String,
        message: String,
    }

app.post(':id/chat', (req,res) => 
{
    //something here
});

I have livegame and matchmaking as separate .js files alongside the index.js whom I call to launch them programmatically as multiple instances. However, this is the error I get:

node:events:505
      throw er; // Unhandled 'error' event
      ^

Error: listen EADDRINUSE: address already in use :::443
    at Server.setupListenHandle [as _listen2] (node:net:1380:16)
    at listenInCluster (node:net:1428:12)
    at Server.listen (node:net:1516:7)
    at C:\Users\----\Documents\MG\src\server-matchmaking.js:25:66
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Emitted 'error' event on Server instance at:
    at emitErrorNT (node:net:1407:8)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
  code: 'EADDRINUSE',
  errno: -4091,
  syscall: 'listen',
  address: '::',
  port: 443
}

Based on my progress so far, I think the issue is with me creating more "dining space" instead of hiring more "workers".

How do I properly create instances of a server with proper load balancing using clusters?

Solution

While I might not go with clustering, here is how I fixed this piece of code.

const https = require('https');
const fs = require('fs');
const cluster = require('node:cluster');
const numCPUs = require('node:os').cpus().length;
const process = require('node:process');

const livegame = require('./server-livegame');
const matchmaking = require('./server-matchmaking');

//Start ExpressJS
var express = require('express');
const { match } = require('assert');

var app = express();
app.use(express.json());


if (cluster.isPrimary) {
  console.log(`Primary ${process.pid} is running`);

  for (let i = 0; i< numCPUs; i++){
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died`);
  });
}
else
{
  var httpsServer = https.createServer(options, app);
  httpsServer.listen(443);

  livegame(app);
  matchmaking(app);

}

with livegame/matchmaking.js looking like

const https = require('https');
const fs = require('fs');
const mongoose = require('mongoose');

//Import Models
const LiveMatch = require('./models/livematch');

//Start ExpressJS
var express = require('express');
const { match } = require('assert');

this.serverLiveGame = function(app)
{

    const chat = 
    {
        puuid: String,
        name: String,
        roleID: String,
        message: String,
    }

    app.post(':id/chat', (req,res) => 
    {

    });
};

module.exports = this.serverLiveGame;

There was more than one mistake in the code:

  1. There can be only be one express() function handling the API for a given server. This value should be constant for any related APIs running under the same port. In my implementation, I simply pass the variable to the functions as a parameter.
  2. To properly pass the api to the worker method from another .js, they must be passed on as a module with a proper module.exports. I wrapped the necessary code in a function so I only need to call one thing per api.
  3. Since we are calling a function and not creating a new object, "new" is incorrect. We call it as any other function with function(param);

Thank you for all the help!

Answered By – NoThrottle

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published