Example of preforking HTTP server using Web Workers

280 views
Skip to first unread message

Peter Griess

unread,
Jun 8, 2010, 7:40:35 PM6/8/10
to nodejs
As promised earlier, I've got some sample code demonstrating how to build a preforking HTTP server using Web Workers and the new net.Server.listenFD() API. I'm hoping that this is a good example of how easy it is to utilize mutliple cores by combining FD sending and Web Workers.

In master.js, we create a socket bound to port 8080 and spawn off 8 workers to handle requests. We send a message to each worker with a banner string to use when responding to requests, as well as the file descriptor for our socket.

var path = require('path');
var netBindings = process.binding('net');
var Worker = require('webworker').Worker;

var fd = netBindings.socket('tcp4');
netBindings.bind(fd, 8080);
netBindings.listen(fd, 128);

for (var i = 0; i < 3; i++) {
    var w = new Worker(path.join(__dirname, 'worker.js'));
    w.postMessage({ 'banner' : 'Hello, world!' }, fd);
}

In worker.js, we create an HTTP server instance, but don't call listen(). Intead, we wait for a message to be received from the parent. We then take the FD from the message and use it to bind the http.Server instance to the socket. As soon as http.Server.listenFD() is invoked, this process will begin handling requests.

var assert = require('assert');
var http = require('http');

var banner = undefined;

var srv = http.createServer(function(req, resp) {
    resp.writeHead(200, {'Content-Type' : 'text/plain'});
    resp.write(banner + ' (pid ' + process.pid + ')\n');
    resp.end();
});

onmessage = function(msg) {
    assert.ok(msg.fd && msg.fd > 0);

    banner = msg.data.banner;

    srv.listenFD(msg.fd);
};

When running master.js, we can use curl to verify that requests are indeed being served by different processes.

Hello, world! (pid 27727)
Hello, world! (pid 27728)
Hello, world! (pid 27729)
Hello, world! (pid 27727)

Easy as pie.

Peter

Tim Smart

unread,
Jun 8, 2010, 8:53:16 PM6/8/10
to nodejs
Well done. Looks great.

Is this patch currently on ry/node/master? Or do you have your own
branch somewhere?

Tim.

Peter Griess

unread,
Jun 8, 2010, 9:09:39 PM6/8/10
to nod...@googlegroups.com
The net.Server.listenFD() code is in ry/node/master, but not yet part of an official release.

Peter

--
You received this message because you are subscribed to the Google Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com.
To unsubscribe from this group, send email to nodejs+un...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/nodejs?hl=en.


Neville Burnell

unread,
Jun 8, 2010, 9:46:24 PM6/8/10
to nodejs
Excellent stuff.

>> In master.js, we create a socket bound to port 8080 and spawn off 8 workers

Sorry to be picky, but the code creates, 3 workers, not 8.

Neville Burnell

unread,
Jun 8, 2010, 9:53:19 PM6/8/10
to nodejs
PS, It would be great to see some simple load testing against 1,2, ..
n workers on an n core system to see how this scales.
> > nodejs+un...@googlegroups.com<nodejs%2Bunsu...@googlegroups.com>
> > .

Aaron Heckmann

unread,
Jun 8, 2010, 10:08:24 PM6/8/10
to nod...@googlegroups.com
Yeah, interested in some benchmarks. Great stuff!

To unsubscribe from this group, send email to nodejs+un...@googlegroups.com.

For more options, visit this group at http://groups.google.com/group/nodejs?hl=en.




--
Aaron
http://clickdummy.net

norlin

unread,
Jun 9, 2010, 8:24:38 PM6/9/10
to nodejs
Hm, it's great, but when I try to terminate one of workers, node is
crashing :(

events:11
throw arguments[1];
^
Error: ECONNREFUSED, Connection refused
at doConnect (net:840:19)
at Stream.connect (net:913:30)
at Object.createConnection (net:619:5)
at Object.<anonymous> (<...>/webworker-child.js:106:13)
at Module._compile (module:384:23)
at Module._loadScriptSync (module:393:8)
at Module.loadSync (module:296:10)
at Object.runMain (module:447:22)
at node.js:208:10


On Jun 9, 3:40 am, Peter Griess <p...@std.in> wrote:
> As promised earlier, I've got some sample code demonstrating how to build a
> ...
> Peter

norlin

unread,
Jun 9, 2010, 8:27:14 PM6/9/10
to nodejs
Hm, it's great, but when I try to terminate one of workers, node is
crashing :(

events:11
throw arguments[1];
^
Error: ECONNREFUSED, Connection refused
at doConnect (net:840:19)
at Stream.connect (net:913:30)
at Object.createConnection (net:619:5)
at Object.<anonymous> (/Users/norlin/Yandex/node/lib/webworker-
child.js:106:13)
at Module._compile (module:384:23)
at Module._loadScriptSync (module:393:8)
at Module.loadSync (module:296:10)
at Object.runMain (module:447:22)
at node.js:208:10

On Jun 9, 3:40 am, Peter Griess <p...@std.in> wrote:
> As promised earlier, I've got some sample code demonstrating how to build a
> ...
> Peter

Erik Corry

unread,
Jun 10, 2010, 2:10:17 AM6/10/10
to nod...@googlegroups.com
2010/6/9 Peter Griess <p...@std.in>:

> As promised earlier, I've got some sample code demonstrating how to build a
> preforking HTTP server using Web Workers and the new net.Server.listenFD()
> API. I'm hoping that this is a good example of how easy it is to utilize

This is interesting. How are these web workers related to the ones in
HTML5? Same API?

--
Erik Corry

levi

unread,
Jun 10, 2010, 8:49:32 AM6/10/10
to nodejs
Really nice. Just for my understanding I tried something similar but
passing the file descriptor to the child process over argv, which
gives me a EBADF, Bad file descriptor. Do you know why?

$ cat test.js
var http = require("http"),
net = process.binding("net"),
sys = require("sys"),
proc = require("child_process");

function master () {
var fd = net.socket("tcp4");
net.bind(fd, 8000);
net.listen(fd, 128);
sys.log("master fd = " + fd);

var p = proc.spawn("node", [__dirname + "/test.js", "slave", fd]);
var pid = p.pid;
p.addListener("exit", function (code, signal) {
sys.log("process " + pid + " died");
});
p.stderr.addListener("data", function (data) {
sys.log(data.toString("utf8", 0, data.length));
});
p.stdout.addListener("data", function (data) {
sys.log(data.toString("utf8", 0, data.length));
});
}

function slave (fd) {
sys.puts("pid " + process.pid + ", fd = " + fd);
var srv = http.createServer(function(req, resp) {
resp.writeHead(200, {'Content-Type' : 'text/plain'});
resp.write('pid ' + process.pid + ')\n');
resp.end();
});
srv.listenFD(fd);
}

switch (process.argv[2]) {
case "master" : master(); break;
case "slave" : slave(parseInt(process.argv[3]));
}

$ node test.js master
10 Jun 14:45:18 - master fd = 5
10 Jun 14:45:18 - pid 37465, fd = 5

10 Jun 14:45:18 -


10 Jun 14:45:18 - net:1070

10 Jun 14:45:18 - throw e;

10 Jun 14:45:18 -
10 Jun 14:45:18 - ^

10 Jun 14:45:18 - Error: EBADF, Bad file descriptor
at IOWatcher.callback (net:1063:24)
at node.js:221:9

10 Jun 14:45:18 - process 37465 died

Also what's the reason for require vs. process.binding?

Many thanks,
Levi

Peter Griess

unread,
Jun 10, 2010, 10:49:29 AM6/10/10
to nodejs
File descriptor passing is a special feature of UNIX domain sockets when used with the sendmsg(2) system call. The kernel performs special voodoo when handling this system call with the please-send-a-file-descriptor-flag set. File descriptors can only be received by the recvmsg(2) system call.

The upshot of all this is that simply passing a number to another process isn't going to work.

Check out node_net.cc for how Node.js implements sending and receiving of file descriptors


--

tedx

unread,
Jun 10, 2010, 4:56:56 PM6/10/10
to nodejs
Cool so I ought be be able to use this capability to run node servers
out of xinetd.

Ted
Reply all
Reply to author
Forward
0 new messages