Skip to content

Instantly share code, notes, and snippets.

@Raynos
Last active August 29, 2015 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Raynos/035f07c5f9d925e36496 to your computer and use it in GitHub Desktop.
Save Raynos/035f07c5f9d925e36496 to your computer and use it in GitHub Desktop.
A c relay server

libuv relay server

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

Interface

Parser.onFrame

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 then sendVolatileFrame() synchronously for fast forwarding.
  • Call persistent() and get a persistent PersistentFrame that has all the needed fields so that you can pass it to an endpoint handler

Parser.manageSocket(handle)

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.

Parser.sendVolatileFrame(handle, VolatileFrame)

For doing efficient forwarding you can mutate the VolatileFrame emitted by onFrame and send it directly to a different handle.

Parser.sendPersistentFrame(handle, PersistentFrame)

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.

Big ideas

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.

Volatile Frame vs Persistent Frame

Volatile Frame

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 memory
  • id a mutable int32
  • type 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 i32
  • serviceName an immutable utf8 string
  • callerName an immutable utf8 string

Persistent frame

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.
@jcorbin
Copy link

jcorbin commented Jul 12, 2015

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.

@Raynos
Copy link
Author

Raynos commented Jul 12, 2015

@jcorbin

I plan to make a change in semantics. If a connection is not identified; synchronously send a Declined error frame instead of queing the in request.

This will simplify the C code interaction a lot.

@Raynos
Copy link
Author

Raynos commented Jul 12, 2015

I edited this document to rename lazy/eager to volatile/persistent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment