How Fugue Works

omni5cience edited this page Feb 13, 2011 · 10 revisions

Fugue is Unicorn for node.js. It's pretty simple (around 300 lines of code at the time of writing), but the code can be a bit hard to read if you don't know what's going on. So here I'll attempt to explain the principles and protocols behind fugue. Again, it's not complicated, just a bit convoluted. Here we go:

Master boot

You pass in a node.js server into fugue.start, together with port or socket path. Something like this:

var fugue = require('fugue'),
    net =   require('net');

var server = net.createServer(function(conn) {

fugue.start(server, 4000, null, 2, {verbose : true});

Remember, you don't call listen on your server, fugue does that for you. Once you call fugue.start, your initial process is the master process, and this is what happens:

1. Bind and listen

The master binds your socket and then calls listen on it, informing node.js that the socket is accepting connections. Now the clock is ticking.

2. Master socket

Next, the master creates a unix socket inside the /tmp dir (or any other dir you pass in options.tmp_path). This socket serves as a way to pass the server socket around, into workers and into new spawned masters (more on this later).

(This is an old UNIX trick, where you can use UNIX sockets to pass a file descriptor around.)

The master listens on this socket. If anyone connects and sends 'GIMME_SOCKET' to this socket, the master sends him the server file descriptor.

3. Spawn workers

Then the number of requested workers are spawned.

Currently this is done by calling the node child_process.spawn, passing in the same arguments as the master process, but slightly changing the environment variables so that the worker knows it's a worker.

Also, inside the environment variables goes:

  • master socket path - for contacting the master requesting the server file descriptor
  • master PID

4. Worker requests server file descriptor

Worker sends "GIMME_SOCKET" request to master via the master socket. Master replies with server file descriptor.

5. Worker tells server to listen using new file descriptor

Now worker has the server file descriptor. Using server.listenFD(file_descriptor) he can do exactly that. Now worker is ready to accept connections on that socket.

6. Live action

The OS, when he gets a connection on the server socket, activates one of the workers. The beauty of this is that the OS takes care of load balancing. We just have to sit and wait for connections.

7. What happens when a worker dies?

The master listens to workers deaths, and respawns them, using the exact mechanism described in .3

8. What happens when the master dies?

When the master dies, he kills all workers. Good bye!

9. Automatic app reload FTW!

But, when he gets a USR2 signal, he restarts your app. Why is this? How is it done?

If you want to restart your app because your code or configuration might have changed, send the master process a USR2 signal like this:

kill -USR2 <master pid>

When master catches this this is what happens:

  • Master spawns a new master
  • New master requests server file descriptor to old master using the protocol described in .2
  • New master spawn new workers as described in .3
  • All new workers request server file descriptor to new master as described in .4
  • Last of the workers kills original master

10. What happens when worker dies?

When ordered to shutdown, a worker does the following:

  • Remove itself from listening to more connections
  • Wait until all connections are closed
  • Die

And voilà!, your code is reloaded with zero downtime.