JOSHUA HOLBROOK github : twitter

An RMI in DNode: Behind the scenes (posted 26 Jun 2011)


I’ve been working on dnode-python (very slowly) for quite some time now, and I took a look at it again tonight. I realized that, in order to write it propers, I needed to figure out the chain of events that happens when something is executed on the remote.

So, suppose we have the following client:

DNode.connect(5050, function (remote) {
    remote.ping( function (msg) {
        console.log(msg);
    });
});

and the following server:

DNode({
    ping: function(cb) {
        b(42);
    }
}).listen(5050);

So, what you would expect to happen if you ran the client and it successfully connected to the server would be that the server would respond with “42”, and the client would print it to the screen.

But what actually happens in the background?

If you are actually interested in understanding the protocol, keep the spec for the protocol handy.

On Connection:

When the connection is first established, the peers swap “methods” messages, which describe their exposed methods. So, in this case, the server would share the following message with the client:

{
  "method": "methods",
  "arguments": [ { "ping", "[function]"} ],
  "callbacks": { "0": [0, "ping"] }
}

The value of the “method” field, “methods”, signals that the server is sharing its methods and not trying to make an RPC. Meanwhile, the “arguments” field contains a scrubbed JSON serialization of the shared object itself. Any functions are scrubbed out and replaced with some sort of placeholder. In this case, the placeholder is simply “[function]”, but the dnode protocol does not specify any particular placeholder. Finally, the “callbacks” field indicates labels for functions, and their locations in the object. In this instance, the “ping” function is given the name “0” and corresponds to the placeholder at x[“arguments”][0][“ping”].

The RMI Part:

Now, revisiting the client:

DNode.connect(5050, function (remote) {
    remote.ping( function (msg) {
        console.log(msg);
    });
});

The callback to DNode.connect passes an object called “remote”, which has a method named “ping” that corresponds to the actual object on the server. When “remote.ping” is called, here’s what happens.

“remote.ping” broadcasts the following message to the server:

{
  "method": "ping",
  "arguments": ["[function]"],
  "callbacks": { "2": [0]}
}

In this case, the field names should make more sense. The method being called is “ping”, the argument passed to ping is a single callback function, and the callback, named “2”, corresponds to x[“arguments”][0].

The server responds with the following message:

{
  "method": "2",
  "arguments": [42],
  "callbacks": {}
}

In this case, the “method” is the function named “2”, the argument for this function is 42, and there are no callbacks to speak of. With this, the client executes function “2” with the argument of 42, which prints “42” to the screen.

WILD!

What did I learn?

My take-home lesson is that giving names to functions and methods is a very important part of DNode. When I first started writing on dnode-python, I knew that dnode needed functions to be named, but I guess it didn’t occur to me as to how big a part of DNode this actually was. Before, I considered the problem to be largely a part of converting objects into DNode messages, but I think the more significant problem may actually have to do with keeping a lookup table of unique keys assigned to pretty much every function, lambda, method and/or built-in that one may have the ability to get involved in dnode.