1

I'm trying to understand how to turn a standalone script into a function that I can then use inside another piece of code (it wil be an express route to be exact) and I believe this means I will need to use an async function and await.

For some context of events, I'd like to spawn the child processes after an emit is received to do the work rather than have the processes spawn on the launch of the script and then return the hash calculated in one of the child processes to emit back to the server.

As you can see in my code I have wrapped the script into a function and then add that to async main at the bottom, and then call the function as await. Sometimes it works and I get the "promise is resolved" message, however sometimes it doesn't work and I just get "all workers finished" stdout.

My question is, does the code seem right and can any veterans see where I'm going wrong here? I've been racking my brains for the past 24 hours trying to understand it but I feel it's beyond my comprehension which is disheartening.

If it's any use, I ran the same script on Windows and saw it error'd with the following (but the same script just running out of the function works fine) so I'm thinking that maybe my Linux box is erroring for the same thing, though I'm not sure about how to go about debugging this.. My devbox is headless so not sure how to get it to listen on 0.0.0.0 instead of localhost and it seems inspect doesn't work on Windows :(

internal/process/per_thread.js:198
      throw errnoException(err, 'kill');
      ^

Error: kill ESRCH
const crypto = require('crypto');
const SHA256 = require('crypto-js/sha256');

function computeHash(index, lasthash, timestamp, data, nonce) {
        return SHA256(
         index +
         lasthash +
         timestamp +
          JSON.stringify(data) +
         nonce).toString();
  }

let index = 0;
let lasthash = "123123123123123";
let timestamp = Date.now();
let data = {"to": "Tom", "from": "Joe", "amt": "999"};
let nonce = 0;
var hash = [];

function cpuWork() {
  return new Promise(function(res, rej) {

        const cluster = require('cluster')
        const numCPUs = require('os').cpus().length

        if (cluster.isMaster) {

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

             cluster.on('exit', function(worker, code, signal) {
             for (var id in cluster.workers) {
                let process_id = cluster.workers[id].process.pid;
                process.kill(process_id);
                }
                console.log("All workers finished");
             });

           worker.on("message", (msg) => {
                console.log(msg);
                if (msg.hash) {
                    console.log(msg.hash);
                        hash = msg.hash;
                        res(hash);
                }
            });

        } else {

           process.on('message', function(msg) {
              console.log('Master ' + process.pid + ' received message from worker ' + this.pid + '.', msg);
            });

             console.log(`Worker ${process.pid} started`)
             console.log(cluster.worker.id);
             let difficulty = 5;
             i = cluster.worker.id;
             var start = new Date();
             var hrstart = process.hrtime();
             hash = computeHash(index, lasthash, timestamp, data, i);
             let pause = 0;
                     while (hash.substring(0, difficulty) !== Array(difficulty + 1).join("0") ) {
                        hash = computeHash(index, lasthash, timestamp, data, i);
                        i = i + cluster.worker.id;
                        }

             var end = new Date() - start,
             hrend = process.hrtime(hrstart);
             console.info('Execution time (hr): %ds %dms', hrend[0], hrend[1] / 1000000)
             console.log("Hash found from: " + i);
             process.send({ hash: hash });
             process.exit(0);
             }
  });
}

async function main() {
        let answer = await cpuWork();
        console.log("Promise resolved: " + answer);
        //console.log(hash);
}
main();
Dominik
  • 6,078
  • 8
  • 37
  • 61
orgg
  • 97
  • 1
  • 9
  • 1
    Worth reading https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it – Emilio Grisolía Dec 09 '20 at 22:07

1 Answers1

1

Your process.kill(process_id); was causing the error kill ESRCH, There is a similar issue of kill call targets a non-existing PID: causes kill ESRCH error.

I tried replacing the process.kill with cluster kill by worker id, which seems to kill the workers without throwing an error.

for (var id in cluster.workers) {
    let process_id = cluster.workers[id].process.pid;
    process.kill(process_id);
    console.log(`worker with processId: ${process_id} is finished`);
}

Replaced the above on with below code:

for (var id in cluster.workers) {
    cluster.workers[id].kill();
    console.log(`worker with processId: ${id} is finished`);
}

This is your updated code with worker kill, I couldn't resolve the initial undefined log issue, it seems to trigger as soon as the main method is called.

const crypto = require('crypto');
const SHA256 = require('crypto-js/sha256');

function computeHash(index, lasthash, timestamp, data, nonce) {
    return SHA256(
        index +
        lasthash +
        timestamp +
        JSON.stringify(data) +
        nonce).toString();
}

let index = 0;
let lasthash = "123123123123123";
let timestamp = Date.now();
let data = { "to": "Tom", "from": "Joe", "amt": "999" };
var hash = [];
const cluster = require('cluster')
const numCPUs = require('os').cpus().length

const cpuWork = async () => {
    if (cluster.isMaster) {
        for (let i = 0; i < numCPUs; i++) {
            var worker = cluster.fork();
        }
        cluster.on('exit', function (worker, code, signal) {
            
            for (var id in cluster.workers) {
                cluster.workers[id].kill();
                console.log(`worker with processId: ${id} is finished`);
            }
            process.exit(0);

        });
        worker.on("message", (msg) => {
            console.log(msg);
            if (msg.hash) {
                console.log(msg.hash);
                hash = msg.hash;
                return (hash);
            }
        });

    } else {

        process.on('message', function (msg) {
            console.log('Master ' + process.pid + ' received message from worker ' + this.pid + '.', msg);
        });

        console.log(`Worker ${process.pid} started`)
        // console.log(cluster.worker.id);
        let difficulty = 5;
        i = cluster.worker.id;
        var start = new Date();
        var hrstart = process.hrtime();
        hash = computeHash(index, lasthash, timestamp, data, i);
        let pause = 0;
        while (hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {
            hash = computeHash(index, lasthash, timestamp, data, i);
            i = i + cluster.worker.id;
        }

        var end = new Date() - start,
            hrend = process.hrtime(hrstart);
        console.info('Execution time (hr): %ds %dms', hrend[0], hrend[1] / 1000000)
        console.log("Hash found from: " + i);
        process.send({ hash: hash });
        process.exit(0);;
    }
}

async function main() {
    let answer = await cpuWork();
    return answer;
}
main().then((result) => {
    console.log(`Promise resolved: ${result}`);
}).catch(e => {
    console.log(e);
});
kgangadhar
  • 4,886
  • 5
  • 36
  • 54
  • Ohh okay I see, so main would have to be handled with .then and a catch? I've ran it on both Linux and Windows and it does seem to resolve everytime now - however it seems that the child processes no longer get killed so the others continue to calculate. I seem to be getting "Promise resolved: undefined" as soon as I run the script too which makes it seem like it isn't being run async? I'm running node v10.15.3 (Windows) and v10.23.0 on Linux if that helps – orgg Dec 09 '20 at 22:25
  • I did a little further testing and put some debug console.logs and it looks as if the exit event isn't triggered unless the answer is found within maybe 5 seconds (setting the difficulty lower). If i leave it set to difficulty 5 then it's hit and miss as to whether the cluster.on('exit') is hit – orgg Dec 09 '20 at 23:06
  • I updated the answer, the workers are killed but the main process will remain that will compute the hash values before it stops. – kgangadhar Dec 10 '20 at 05:05
  • Is there any way to return msg.hash (from worker.on) instead of hash from process.on? Basically, if I use clusters.workers[id].kill the other processes don't get killed until they finish computing. On Windows this seems to be okay, but for Linux the child process continues to run. It seems the "exit" event isn't being fired which I assume is because of the return statement. In this case I wouldn't mind using a global variable or something as a hack but can't seem to figure that out – orgg Dec 10 '20 at 10:33