8

I would like to separate server high consuming CPU task from user experience:

./main.js:

var express = require('express');
var Test = require('./resources/test');
var http = require('http');
var main = express();

main.set('port', process.env.PORT || 3000);
main.set('views', __dirname + '/views');
main.use(express.logger('dev'));
main.use(express.bodyParser());
main.use(main.router);

main.get('/resources/test/async', Test.testAsync);

main.configure('development', function() {
  main.use(express.errorHandler());
});

http.createServer(main).listen(main.get('port'), function(){
  console.log('Express server app listening on port ' + main.get('port'));
});

./resources/test.js:

function Test() {}
module.exports = Test;

Test.testAsync = function(req, res) {
  res.send(200, "Hello world, this should be sent inmediately");
  process.nextTick(function() {
    console.log("Simulating large task");
    for (var j = 0; j < 1000000000; j++) {
      // Simulate large loop
    }
    console.log("phhhew!! Finished!");
  });
};

When requesting "localhost:3000/resources/test/async" I would expect the browser rendering "Hello world, this should be sent inmediately" really fast and node.js to continue processing, and after a while in console appearing "finished" message.

Instead, browser keeps waiting until node.js finishes large task and then renders the content. I've tried with res.set({ 'Connection': 'close' }); and also res.end(); but nothing works as expected. I've also googled with no luck.

How should it be to send the response to client immediately and server continue with tasks?

EDIT

posted fork method in solution

Cœur
  • 37,241
  • 25
  • 195
  • 267
Miquel
  • 8,339
  • 11
  • 59
  • 82

3 Answers3

6

Try waiting instead of hogging the CPU:

res.send("Hello world, this should be sent inmediately");
console.log("Response sent.");
setTimeout(function() {
  console.log("After-response code running!");
}, 3000);

node.js is single-threaded. If you lock up the CPU with a busy loop, the whole thing grinds to a halt until that is done.

Peter Lyons
  • 142,938
  • 30
  • 279
  • 274
  • Thanks, but what I exactly need is to separate intese CPU tasks from user experience. So user gets response really fast while server continues processing the request. – Miquel Jan 22 '14 at 16:38
  • 1
    "processing" is fine. Normal processing is a little CPU time then a lot of I/O time and node works fine with that. Node does NOT work with all-CPU workloads like crypto, image processing, etc. You need to fork CPU-intensive workloads off to separate processes so your node web server can remain responsive. I suspect you don't truly have all-CPU workload to do, and you just have a misconception, but if so, node can't do that stuff in-process. – Peter Lyons Jan 22 '14 at 17:55
  • That's what I'm looking for: forking. Question is, in a very high responsive server I would like to serve the response to the client as soon as it is baked. Response time is a critical point: the clients can reject the response if it takes more than they expect. So I would like to respond and then do some work on database. That's most critical when we talk of thousands of qps so here it comes forking or even balancing. I was expecting that `process.nextTick(f)` will do the trick, going to try also `setTimeout(f,0)` – Miquel Jan 22 '14 at 18:46
  • 1
    Well, again, if your workload is CPU-intensive, neither `process.nextTick` nor `setTimeout` are viable. You must either use the `child_process` module to spawn separate worker processes - separate OS processes being essential. Or you must use some type of workload queueing system like zeromq, rabbitmq or similar. – Peter Lyons Jan 22 '14 at 19:03
  • Could you provide a working sample of forking? For the moment the farest I've reached is getting "cannot write to IPC channel" when trying the sample from [child](http://nodejs.org/api/child_process.html) sending {hello: "world"} message to child – Miquel Jan 22 '14 at 19:09
  • Your snippets work for me minus the HTTP bits on OSX. Your basic `fork/send` code looks OK. Are you on windows? – Peter Lyons Jan 22 '14 at 19:40
  • Hm.. not in nodeclipse for Windows – Miquel Jan 22 '14 at 20:02
  • Here's a pretty informative [blog post](http://www.andygup.net/node-js-moving-intensive-tasks-to-a-child-process/) about spawning a child_process (i.e. similar to forking) which would solve the CPU hogging part, but obviously not the early response that the question is based on.. – gumaflux Apr 30 '14 at 12:57
5

Thakns for Peter Lyons help, finally the main problem was firefox buffer: response was not so long as to flush it (so firefox kept waiting).

Anyway, for hight CPU performing tasks, node would keep hanged until finishing, so will not be attending new requests. If someone needs it, it can be achieved by forking (with child_process, see sample in http://nodejs.org/api/child_process.html)

Have to say that change of context by forking could take longer than splitting the task in different ticks.

./resources/test.js:

var child = require('child_process');
function Test() {}
module.exports = Test;

Test.testAsync = function(req, res) {
  res.send(200, "Hello world, this should be sent inmediately");
  var childTask = child.fork('child.js');
  childTask.send({ hello: 'world' });
};

./resources/child.js:

process.on('message', function(m) {
  console.log('CHILD got message:', m);
});
Miquel
  • 8,339
  • 11
  • 59
  • 82
2

A good solution is to use child_process.fork(): it allows you to execute another JavaScript file of your app in a different Node instance, and thus in a different event loop. Of course, you can still communicate between the two processes by sending messages: so, from your UI process, you can send a message to the forked process to ask it to execute something.

For example, in ui.js:

var ChildProcess = require('child_process');
var heavyTaskWorker = ChildProcess.fork('./heavyTaskWorker.js');
...
var message = {
    operation: "longOperation1",
    parameters: {
        param1: "value1",
        ...
    }
};
heavyTaskWorker.send(message);

And in heavyTaskWorker.js:

process.on('message', function (message) {
    switch (message.operation) {
    case 'longOperation1':
        longOperation1.apply(null, message.parameters);
        break;
    ...
    }
});

Tested here, and it works fine!

Hope that helps!

FruityFred
  • 241
  • 2
  • 8