ref – https://www.digitalocean.com/community/tutorials/how-to-launch-child-processes-in-node-js
Creating a Child Process with fork()
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
const http = require('http'); const host = 'localhost'; const port = 8000; // this function will take some time to run const slowFunction = () => { let counter = 0; while (counter < 5000000000) { counter++; } return counter; } const requestListener = function (req, res) { if (req.url === '/total') { let slowResult = slowFunction(); let message = `{"totalCount":${slowResult}}`; console.log('Returning /total results'); res.setHeader('Content-Type', 'application/json'); res.writeHead(200); res.end(message); } else if (req.url === '/hello') { console.log('Returning /hello results'); res.setHeader('Content-Type', 'application/json'); res.writeHead(200); res.end(`{"message":"hello"}`); } }; const server = http.createServer(requestListener); server.listen(port, host, () => { console.log(`Server is running on http://${host}:${port}`); }); |
Fork is a variation of spawn. To create a process that’s also a Node Process.
Main benefit of fork over (exec, spawn) is that fork enables communication between the parent and child process.
CPU intensive tasks (iterating over large loops, parsing large JSON) stop other JS code from running. If a web server is blocked, it cannot process any new incoming requests until the blocking code has completed its execution.
1) Now run the server
2) run /hello (1st time)
3) run /total
4) run /hello (2nd time)
So we’ll only see the result from the first /hello. The slow function will block other code, and that’s why we won’t see the 2nd /hello.
The blocking code is slowFunction.
So how do we solve this?
Move blocking code to own module:
getCount.js
1 2 3 4 5 6 7 8 |
const slowFunction = () => { let counter = 0; while (counter < 50000000000) { console.log(`counter - ${counter}`); counter++; } return counter; } |
Since it will be run with fork, we can add code to communicate with the parent process when slowFunction has completed processing. Let’s send a message to the parent process with JSON message.
1 2 3 4 5 6 7 8 |
process.on('message', (message) => { if (message == 'START') { // server will do: child.send('START'), so we must receive console.log('Child process received START message'); let slowResult = slowFunction(); // upon receiving START, we run our long task let message = `${new Date().toISOString()} {"totalCount":${slowResult}}`; process.send(message); // send data back to the parent process } }); |
– Why look for message ‘START’? Our server code will send the START event when someone access the ‘/total’ endpoint.
– Upon receiving that event, we run slowFunction().
– we use process.send() to send a message to the parent process.
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
const http = require('http'); const { fork } = require('child_process'); const host = 'localhost'; const port = 8000; let child; const requestListener = function (req, res) { if (req.url === '/total') { console.log(` ${new Date().toISOString()} - Start slow job`); child = fork(__dirname + '/getCount'); console.log('child process id: ' + child.pid); child.on('message', (message) => { console.log('Returning /total results'); res.setHeader('Content-Type', 'application/json'); res.writeHead(200); res.end(message); }); child.send('START'); } else if (req.url === '/hello') { console.log('Returning /hello results'); res.setHeader('Content-Type', 'application/json'); res.writeHead(200); res.end(`${new Date().toISOString()} {"message":"hello"}`); } else if (req.url === '/kill') { if (child) { child.kill("SIGKILL"); console.log('kill the child process!!!!!!'); res.setHeader('Content-Type', 'application/json'); res.writeHead(200); res.end(`${new Date().toISOString()} {"message":"process killed"}`); } } } const server = http.createServer(requestListener); server.listen(port, host, () => { console.log(`Server is running on http://${host}:${port}`); }); |
getCount.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
const slowFunction = () => { let counter = 0; while (counter < 50000000000) { console.log(`counter - ${counter}`); counter++; } return counter; } process.on('message', (message) => { if (message == 'START') { console.log('Child process received START message'); let slowResult = slowFunction(); let message = `${new Date().toISOString()} {"totalCount":${slowResult}}`; process.send(message); } }); process.on('beforeExit', (code) => { console.log('Process beforeExit event with code: ', code); }); process.on('exit', (code) => { console.log('Process exit event with code: ', code); }); |
Run the app: node index.js
Open a 2nd terminal and type: curl http://localhost:8000/total
This will start up a long running task at say 7:41:18.
Open a 3rd terminal and type: curl http://localhost:8000/hello
Hi it a bunch of times. As you can see, it is running concurrently and not blocking the main JS code. This is because its a child process that’s running it concurrently. From the time stamp, you will also see that it’s happening after 7:41:18.
Then when the long task is done, it stops at 7:42:20. This proves that the /hello logs before 7:42:20 were all done concurrently with the task at /total.