Mevia
Mevia

Reputation: 1564

node.js force only one thread to execute code

When i start my application node app.js, the process running has only 1 thread. However longer it runs the more threads are made for the process. The problem is that when i want to execute specific kind of code like this:

var io = require('socket.io')(process.env.PORT);

It fails because signal was sent from multiple threads and therefore code isnt successfully executed.

Simple test, if one do this:

var io = require('socket.io')(9001);
var io = require('socket.io')(9002);
var io = require('socket.io')(9003);
var io = require('socket.io')(9004);

it works alright, but this code:

var cPort = 9001;
setInterval(function() {
    var io = require('socket.io')(cPort);
    cPort++;
}, 1000 * 60 * 2); // 1 sec * 60 seconds * 2 = 2 minutes interval

wont be executed, because after 2 minutes node will have many threads and they all try to execute code - as a result you will see error: address in use.

So despite running multi-thread process of the same file how can i force node to execute this code only once?

06.11.2017 EDIT ----

To clarify the problem:

What i mean in the question, i dont have problem with resources, if i start all the servers at once (for example 40 servers) they are all started successfully and working indefinitely. Problem happens if i start just one server and then run the code that auto starts more when needed. At that point i always see address in use error, ofc obviously address is not in use in the moment of code execution. Currently i have to manually start more servers in weekends when there are more people using service and less servers in other days of week, i wanted to create automated system that starts and closes servers based on population.

this is the code of servers starting:

var cp = require('child_process'),
    servers = [],
    per_server = config.per_server,
    check_servers = function(callback) {
        for(var i = 0; i < servers.length; i++) {
            callback(i, servers[i]);
        }
    };

this.add_server = function(port) {
    var server = {
        port: port,
        load: 0,
        process: cp.fork(__dirname + '/../server_instance.js', [], {
            env: {
                port: port
            }
        })
    };

    server.process.on('message', function(message) {
        server.load = message.load;
    });

    servers.push(server);
};

this.find_server = function() {
    var min = Infinity,
        port = false;

    check_servers(function(index, details) {
        if(details.load < min) {
            min = details.load;
            port = details.port;
        }
    });

    return port;
};

now if i execute controller.add_server() 40 times in row it will start 40 servers correctly, but if i do this:

var start_port = 3185;
setInterval(function() {
    var min = Infinity;

    check_servers(function(index, details) {
        if(details.load < min) {
            min = details.load;
        }
    });

    if(min > config.per_server) {
        controller.add_server(start_port);
        start_port++;
    }
}, 5000);

I get randomly error at second, third or forth server creation that address is already used.

07.11.2017 EDIT ----

As suggested i tried the following libraries for port scan/finder:

Only using first one i was able to start at least 2 servers, this is the code i used:

setInterval(function() {
    var min = Infinity;

    check_servers(function(index, details) {
        if(details.load < min) {
            min = details.load;
        }
    });

    if(min > per_server) {
        _self.add_server();
    }
}, 5000);

var portfinder = require('portfinder');
portfinder.basePort = 3185;

this.add_server = function() {
    portfinder.getPortPromise()
        .then((port) => {
            console.log('port found', port);

            var server = {
                port: port,
                load: 0,
                process: cp.fork(__dirname + '/../server_instance.js', [], {
                    env: {
                        port: port
                    }
                })
            };

            server.process.on('message', function(message) {
                server.load = message.load;
            });

            servers.push(server);

        })
        .catch((err) => {
            console.log('error happened');
        });
};

After many tests performed, it look like i can start 2 servers and then its random, crashes at third or forth attempt. Its clear that problem is deeper then with ports finding, this library is only telling me what i already know, i know what ports are opened, and i double check that before script will try to start server with manual netstat -anp | grep PORT command.

So its clear that problem is not in finding opened ports, from the outcome point of view it looks like node is attempting to start server multiple times from single command.

follow up EDIT ----

adding server_instance.js code:

var io = require('socket.io')(process.env.port),
    connections_current = 0,
    connections_made = 0,
    connections_dropped = 0;

io.on('connection', function(socket) {

    connections_current++;
    connections_made++;

    // ... service logic here, not relevant (like query db, send data to users etc)

    socket.on('disconnect', function() {
        connections_current--;
        connections_dropped++;
    });

});

setInterval(function() {
    process.send({
        load: connections_current
    });
}, 5000);

08.11.2017 EDIT ----

I was testing many solutions to solve the problem and i observed this situation:

This is clearly the source of the problem, the more capacity i have, more processes node spawns and sooner it will overlap when attempting to start new server.

Upvotes: 7

Views: 2652

Answers (2)

revy
revy

Reputation: 4707

You can use portfinder package to discover available network ports in your system (it starts discovering from port 8000). The usage is simple as:

const http = require('http');
const portfinder = require('portfinder');
const pid = process.pid;


portfinder.getPort((err, port) => {
    if (err)
        throw err;

    http.createServer((req, res) => {         
        res.end(`Response from server ${pid}.\n`);
    }).listen(port, () => {
        console.log(`Server ${pid} running on port ${port}...`);
    });    
});



** EDIT **

It seems that the same port is returned multiple times from portfinder, thus the EADDRINUSE error is thrown. My suspect was that the port is not already listening when portfinder tries to find a new one (thus returning the same port), but this seems to be contradicted by the fact that starting multiple servers with a simple for loop seems to work fine:

for (let i = 0; i < max_number_of_servers; ++i) {
    this.add_server();
}


A simple fix to your code could be to increase the base address of portfinder on each call to add_server:

portfinder.basePort = 8000;

this.add_server = function() {
        portfinder.getPortPromise()
        .then((port) => {   

            portfinder.basePort += 1;

            var server = {
                port: port,
                load: 0,
                process: cp.fork('server_instance.js', [], {
                    env: {
                        port: port
                    }
                })
            };

            server.process.on('message', function(message) {
                server.load = message.load;
                console.log("message");
            });

            servers.push(server);

        })
        .catch((err) => {
            console.log(err);  
        });   
};

This code seems to work fine, at least on my machine.
In any case I suggest you to consider a different implementation. Imho if you find that in the highest traffic scenarios you need N servers to handle properly all the requests, there is no need to create a lower number of servers and then dynamically change it in base of the current traffic, for few reasons:

  • A new process is an expensive operation to do and it can take time to be up and running.
  • In case of high traffic all your server are already ready to serve requests without additional delay.
  • In case of low/medium traffic your servers will be less overloaded but you gain in terms of resilieancy and availability (if a server process crash, for whatever reason, there are many other servers that are likely to serve the request while you can start a new server process which takes some time).


You can use the native cluster module to build easily a process-distributed server app with automatic load-balancing and fault-tolerance. By default the clusteer module performs a round-robin algorithm to distribute the incoming requests across the workers, thus you get load-balancing for free!
A possible simple implementation (just for test I've used a different port finder package):

// main.js

const cluster = require('cluster');
const getPort = require('get-port');
const max_servers = 40;

// master process
if (cluster.isMaster) {
    for (let i = 0; i < max_servers; ++i) {
        getPort().then(port => {
            cluster.fork({port: port});
        })          
    }
    // detect exit event on workers
    cluster.on("exit", (worker, errCode) => {
        console.log(worker);
        // start new worker in case of crashes
        if (errCode != 0 && !worker.suicide) {
            console.log("Worker-server crashed. Starting new worker...");
            getPort().then(port => {
                cluster.fork({port: port});
            })
        }
    });
}
// worker process --> start server
else {
    require('./server_instance.js'); // [2]
}
// server_instance.js

const http = require("http");
const pid = process.pid;
let port = process.env.port;

console.log(`Starting server on process ${pid} running on port ${port}...`);

let io = require('socket.io')(process.env.port),
    connections_current = 0,
    connections_made = 0,
    connections_dropped = 0;

io.on('connection', function(socket) {
    console.log(`Socket.io on process ${pid} running on port ${port}...`);
    connections_current++;
    connections_made++;

    // ... service logic here, not relevant (like query db, send data to users etc)

    socket.on('disconnect', function() {
        connections_current--;
        connections_dropped++;
    });

}); 

Upvotes: 0

Jehy
Jehy

Reputation: 4899

The best solution would be to generate port numbers in your master process and then pass them to worker processes so that those don't intersect.

Also you can check if port is in use and get free port using npm module like test-port-provider.

Upvotes: 1

Related Questions