var LibuvTChan = require('libuv-tchannel');
var parse = new LibuvTChan();
// You get frames form the channel
parser.onFrame = onFrame;
// You create a tcp server in node
net.createServer(onConnection);
function onConnection(socket) {
parser.manageSocket(socket._handle);
// tchannel Connection/Channel node.js code
}
// You create out sockets in node
var socket = net.createConnection(host, port);
parser.manageSocket(socket._handle);
// tchannel Connection/Channel node.js code
// You can forward frames through the parser
parser.sendVolatileFrame(socket._handle, VolativeFrame);
// You can also send frames through the parser
parser.sendPersistentFrame(socket._handle, PersistentFrame);
// Any information for stats and logs will be
// sent to javascript
parser.onStats = onStats;
The idea is that all the actual tcp read and write logic is rewritten in C. This removes the overhead of node's TCP implementation.
This also removes all buffer manipulation overhead in node.js
The parser.onFrame function must be set in JavaScript and is a function
that takes a VolatileFrame
.
A VolatileFrame
is backed by buffer in C.
A VolatileFrame
can be one of the N types of frames in the protocol.
For our forwarding use cases the VolatileFrame
has a few fields that can
be read and a few mutable fields. The mutable fields are id
and ttl
.
A VolatileFrame
also has an persistent()
method that returns a
PersistentFrame
object that is fully realized.
For performance reasons the C implementation will recycle the VolatileFrame
immediately after the function call finished.
This means you must do one of two things synchronously:
- Mutate the
VolatileFrame
and thensendVolatileFrame()
synchronously for fast forwarding. - Call
persistent()
and get a persistentPersistentFrame
that has all the needed fields so that you can pass it to an endpoint handler
If you have a TCP Socket in node you can pass the handle to libuv and it will manager the reading of all incoming frames for you.
Every time it reads a frame it calls Parser.onFrame
.
For doing efficient forwarding you can mutate the VolatileFrame
emitted by
onFrame
and send it directly to a different handle.
If you want to send a frame without having any other frames you can do so
with sendPersistentFrame()
. It's expected that the javascript
code has a pool of persistent frame objects that it can mutate and send.
It's safe to assume that the persistent frame can be recycled and mutated again
after the sendPersistentFrame()
call is done.
The big idea here is that a nodejs tchannel relay is just a ringpop cluster that manages connections.
The actual work of parsing TCP and writing to TCP is all handled in a really efficient shared C library.
A Volatile frame is created in C++ and has a piece of memory that is the actual frame buffer associated with it. A VolatileFrame only exposes information to JavaScript that is absolutely needed by the relay code.
All volatile frames have the following fields:
mem
some kind of representation of the memoryid
a mutable int32type
an immutable int8
The size
field is hidden and only available in C++.
For each one of the types of frames a volatile frame supports more information.
In the current case the only frame type that has more information is
CallRequest
which exposes the following fields
ttl
a mutable i32serviceName
an immutable utf8 stringcallerName
an immutable utf8 string
A persistent frame can only be create from JavaScript. There are unique persistent frame constructors for all types of frames; for each persistent frame constructor it has mutable fields for all the pieces of information in the protocol document
There are two ways of creating an persistent frame
- Ask the VolatileFrame to populate an persistent frame object with information from the buffer so that endpoint handlers can do their job and read all data
- Take one of the cached persistent frames meant for writing; set some fields
and call
sendPersistentFrame()
on the parser.
So we currently don't always make a synchronous forwarding decision: in the newly started case where no identified peer exists yet, we essentially queue on the node event loop and wait for a peer to identify itself before finally forwarding the lazy frame.
Now we arguably should decline to process in that case, probably as simple as a "no peer available", which would let us make an always-synchronous decision.