Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

file 127 lines (108 sloc) 8.014 kb
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
IPCNode
=======
Why a new Node.js IPC library?
------------------------------
As of writing, this, there are three libraries available that offer RPC client and server packages:
- Eric Florenzano's node-jsonrpc
- Ryan Tomayko's bertrpc
- James Halliday's dnode

The first two both have a very verbose syntax, requiring client.call('method',arguments,returnvalue), and offer no way to nest several objects, use objects as arguments, or return functions.
dnode is a pretty good fit for most cases, but it never cleans up any marshalled functions or objects, and as such the server will not garbage collect anything unless the connection is closed.
Furthermore, dnode only supports socket.io or socket as transport layers, and as such can not be used over (for example) standard input/output.

Advantages of IPCNode
---------------------
- Explicit reference counting, so no lingering objects.
- Implements the Node.js Stream interface, so you can easily use any transport layer you want.
- Event when there are no more cross-boundary objects, so connection can be closed gracefully.
- Circular objects are fully supported.

Influences
----------
For the whole idea of simple async RPC, I have to thank dnode. Simply defining objects with functions and the ability to simply marshal that to another process is awesome :)
As for the reference counting, I'll have to thank my Win32 COM background for that :p

Why reference counting
----------------------
Dnode will simply assign an ID to any callback function, and on the other side create a stub function that sends the ID over the line.
The problem with that approach is that callback functions will stay in memory until the connection is discarded. Normally with a few simple static callback functions, this isn't a big problem.
However, if you plan on calling in your RPC with anonymous functions, you should note that everytime you call something with an anonymous function, that anonymous function will never be garbage collected.
With reference counting, the server side can indicate when a callback function is no longer needed, and the client can discard it.

To make things easier, IPCNode supplies a helper function for two common approaches:
- IPCNode.sync to convert a normal synchronous function that returns a value to an asynchronous function with an extra callback argument.
- IPCNode.async to convert a normal asynchronous function with an existing callback function, to one that will keep a reference to that callback function and dispose of it when it's called.

Getting started
---------------
To get started, just create an IPC endpoint by doing 'new IPCNode()'. This will yield you a readable and writeable stream, which you should connect to another IPC endpoint in another (or the same process).
The simplest way to do this is to simply set up a socket connection, and call sys.pump.
When the IPCNode is wired up propertly, you can do ipcnode.register(some objects) to pass to the other side. This will trigger the "register" event at the other endpoint.
To determine when there will be no more IPC-communications happening, listen for the "clean" event.

I highly suggest just reading the examples in the examples subdirectory.

Reference counting
------------------
Because in JavaScript IPCNode has no way to know when a marshalled object is no longer used, you will have to explicitly tell it when you are and aren't using objects.
Normally, objects will be valid for the duration of the call from IPCNode. This means that during the "register" events those objects are valid, and during a call all arguments are valid.
If you intend to use an object after the function has returned, you should get a reference to it, you can do so by:
var ref=IPCNode.reference(some object);
This will tell IPCNode that you made a copy of the object, and as such it shouldn't destroy it yet after the function ends.
IPCNode.reference will return the same object, but it helps to treat it as if it was a new copy.
When you are done with an object, you should call IPCNode.dispose(obj).

Internally, IPCNode will keep track of how many times you have referenced an object, and once you called dispose just as much, will destroy the object.
However, it's easier to think about it as as IPCNode.reference making a copy, and IPCNode.dispose properly disposing of that copy.

Note that if a marshalled object contains other objects, there is no need to keep a reference to those objects.
e.g., let's say you had:
var one={
two: {
three: function() {}
}
};
Then
var onecopy=IPCNode.reference(one);
Would ensure that onecopy.two and onecopy.two.three will stay valid as long as onecopy is.

Reference counting helpers
--------------------------
If you have a simple asynchronous function, of which the last argument is a callback function that will be called once, you can use IPCNode.async to wrap it with reference counting.
Basically IPCNode.async will take the last argument given, keep a reference to that, and replace it with a new function that will call the reference and dispose of it once called.

If you have a function that synchronously returns a value, you can wrap it with IPCNode.sync.
This will create a new function with an extra callback argument that will get referenced and called upon return.

Pitfalls
--------
Circular structures are supported, but watch out for circular objects going cross-boundary.
e.g. let's say you have
var a={other: b};
var b={other: a};
and a is in process 1, and b in process 2.
This would mean that process 1 would keep a reference to object b, and process 2 would keep a reference to object a, and as such both objects will never be cleaned by the garbage collection, and the IPCNode will never get clean.

Furthermore, this for functions is properly marshalled.
if you have:
var increasing={counter:0,increase:function() {this.counter++}};
var decreasing={counter:100,decrease:function() {this.counter--}};
Then it will still be possible to get increasing to decrease, for example by doing:
decreasing.decrease.apply(increasing)
This is also possible across process-boundaries, so if functions should not be called on different objects, use something like Function.bind().

Speed vs. bandwidth
-------------------
IPCNode can either send the bare minimum, and have the other side possibly request more information on certain objects when it needs to, or it can send everything every time.
If it only sends the bare minimum, the other side might have to do a few requests before it can successfully marshal a call or register event.
If it sends everything all the time, it'll be sending a lot of information the other side probably already has.
Which roughly translates to:
Sending the bare minimum:
- Overall less bandwidth usage as things can be cached
- Slower, as several round-trips might be needed to negotiate what should be sent.
If you send everything all
- Lots of bandwidth, as even duplicate information will be sent.
- Faster, as no negotiation is needed on what should be sent.
To tell IPCNode to use the bare minimum, set
IPCNode.defaultPrepareCount=0;
To tell IPCNode to always send all, set
IPCNode.defaultPrepareCount=-1;
To tell IPCNode to always send information on the first x objects arguments encountered, set
IPCNode.defaultPrepareCount=x;

There are plans to have IPCNode estimate if the other side already has some information, and only send if it probably doesn't, but those plans are not implemented yet.


Using in the browser
--------------------
IPCNode currently makes heavy use of things like Array.prototype.forEach, and Object.defineProperty, and inherits from EventEmitter.
This means that in its current form, it will not work in the browser.
There are, however, plans to change that :)

TODO
----
TODO:
- Make web-browser proof
- When local object is marshalled for the first time, send some prepared info down the line.
Something went wrong with that request. Please try again.