You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@thrift.apache.org by James King <Ja...@Dell.com> on 2010/08/10 19:09:49 UTC

Endpoints and Channels

I posted about this a while back, I wanted to report with the latest
updates so that people are aware of it.

The code is only in C# at the moment, and is posted to THRIFT-66.  There
is a Visio diagram and jpg version of same inside the package which
illustrates these concepts.  The main difference from the first post is
that the channel specification is NOT in the Thrift IDL.  The channels
are assigned at runtime.   There is quite purposefully no discovery
protocol.

 

Summary of new Concepts:

 

Channels:  Allow you to host multiple Thrift services on a single
connection.

Endpoints: Allow requests from either end of the connection to be
processed; allows multiple outstanding requests.

 

Endpoints

---------

 

Endpoint is a new concept that replaces the C# runtime "Server"
namespace.  There are two types of Endpoints:

 

TConnectedEndpoint represents one side of a connection.  A client will
create one of these to open a connection to a Thrift server.

 

TListeningEndpoint implements a listener.  When a connection comes in,
it spawns a TConnectedEndpoint.

 

Both ends are now able to submit requests to the other.  The Endpoint
mechanism has a dedicated thread.  This thread is responsible for:

 

1.       Picking the next message header off the transport.

2.       Inspect the header

a.       If it is a request, locate the TProcessor for that request
given the function name and channel id.  

                                                               i.
If one cannot be found, throw an appropriate exception.

                                                             ii.
Ask the TProcessor to pull the contents of the request off the wire.

                                                            iii.
Spin up a thread to handle this request.

                                                           iv.      Go
back to Step 1

b.      If it is a reply, look in the waiting reply queue by channel id
and sequence id.

                                                               i.
If one cannot be found, throw an appropriate exception.

                                                             ii.
Deliver the reply to the blocked thread waiting for it.

 

For each spawned request processing created, the response is pushed into
an in-memory transport that holds the entire contents of the response.
Once the TProcessor is done processing the request (or throwing an
exception), the thread acquires the TConnectedEndpoint's outbound mutex
and writes the contents of the memory backed transport to the wire.
Once the response is fully sent, the thread is done.  Replies are
therefore delivered as they are ready, not in the order of the requests.
This is optimal, since we have sequence ids.

 

When a request is made, the current thread acquires the outbound mutex,
places a response handler into a map, pushes the request out, and blocks
on the response handler to signal that it received a reply.

 

 

Channels

--------

 

Channels are not part of the current message protocol specification, so
let us look at a little message header history.  There was an original
binary protocol that looked like this:

 

  begin

    length 4 : length of function name [signed]

    length n : function name

    length 1 : message type

    length 4 : sequence ID

  end

 

When versioning was introduced, the upper bit of the first I32 was
turned on so that all clients could do a signed comparison on the
incoming size.  If less than zero, it is a versioned header, otherwise
it is an old non-versioned header.  The new versioned header we all know
today is:

 

  begin

    length 4 : version which is:

               0x80000000 (high bit set) +

               0x7FFF0000 mask of the actual version +

               0x000000FF mask of the message type

    length n : string [function name]

    length 4 : sequence ID

  end

 

There is an empty byte in the version field which I would like to assign
to a channel ID.  All existing clients today send 0x00 in this byte
today.  Here is how the new VERSION_1 header would look like - note it
would be fully backwards compatible (on channel 0):

 

  begin

    length 2 : 0x8000 + version = VERSION_1 (0x8001)

    length 1 : channel id

    length 1 : message type

    length n : string [function name]

    length 4 : sequence ID

  end

 

To maintain backwards compatibility, Channel 0 will be reserved for
legacy processing.  In the C# runtime, the existing TServer classes will
run unchanged because they implement today's server-only, "channel 0"
behavior.

 

To actually implement a Thrift service on a Channel the Endpoint
provides an event handler in C# that facilitates this setup like this
(from the ChannelTest.cs file included with the patch for THRIFT-66):

 

// ... server setup ...
    m_server = new TListeningEndpoint(tServerSocket,
maxConnectedClients);

    m_server.ClientConnected += m_server_ClientConnect;

    m_server.Start();

 

void m_server_ClientConnect(TConnectedEndpoint client)

{

    client.AddRequestProcessor(echoChannel, new EchoServer.Processor(new
EchoServerImpl()));

    client.AddRequestProcessor(randChannel, new RandServer.Processor(new
RandServerImpl()));

 
client.SetConcurrentInboundRequestLimit(m_maxConcurrentRequestsPerCall);

    client.Disconnected += new
TConnectionDelegate(connectedClient_Disconnected);

}

 

On the client side, you perform this channel setup before connecting:

 

private void SetupClient(int concurrencyLimit)

{

    TSecureSocket originator = new TSecureSocket("localhost", port, 0,
null, null);

    TConnectedEndpoint client = new TConnectedEndpoint(originator);

    client.AddRequestProcessor(echoChannel, new EchoServer.Processor(new
EchoServerImpl()));

    client.AddRequestProcessor(randChannel, new RandServer.Processor(new
RandServerImpl()));

    client.SetConcurrentInboundRequestLimit(concurrencyLimit);

    client.Disconnected += client_Disconnected;

    client.Start();

}

 

My hope is that this can be used as a design template to implement
Channels and Endpoints in other languages.

This has had some exposure to QA inside Dell EqualLogic as part of a
decent sized project.  I would consider it stable.

 

Thanks,

 

________________________________

 

	James E. King, III
Senior Software Engineer
Dell (EqualLogic) HIT Team (ASM) 

 

	300 Innovative Way, Suite 301
Nashua, NH 03062 USA
(603) 589-5895