Channels will enable Thrift to support the following features:

* Facilitate two-way communication - either end can serve or call on a channel.
  Currently one end must be the client, the other must be the server 

* Service multiple thrift services on a single connection.  
  Currently only one service is allowed on a connection.

The final result will end up with new TEndpoint concept that will pick message 
headers off the wire and inspect them.  Given their channel ID and their 
message type, they will be routed to the appropriate request handler or client 
waiting for a response.

Initially I am doing this in the C# runtime.  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.

Because the Channel ID is now an integral part of the service being hosted, I 
also recommend modifying the Thrift IDL for services.  Before it looked like 
this:

[14] Service ::=  'service' Identifier ( 'extends' Identifier )? '{' Function* '}'
 

I would recommend:

[14] Service ::=  'service' Identifier 
                      ( 'channel' IntConstant )?
                      ( 'extends' Identifier )? 
                      '{' Function* '}' 

If the channel is not specified, it is set to zero, which maintains 
compatibility with existing schemas and runtimes.  If an older parser tries to 
parse a newer service with a channel optional argument it will fail, so that is 
safe too.

The existing C# runtime code still works on Channel 0, however there is a new 
set of objects to replace the TServer and its variants.  These are now variants 
of TEndpoint.  Essentialy what happens is:

* A read pump reads off the TTransport and routes requests and replies.

* Client makes a TConnectedEndpoint on a TSocket (or other TTransport)
  Client fires up a read pump to read off the socket

* Server makes a TListeningEndpoint on a TServerSocket (or other 
TServerTransport)
  Server fires up a read pump to listen to the socket
  When server receives a connection, it creates a TConnectedEndpoint for it
    That TConnectedEndpoint is the same type of thing that the client fired up.

As you can see now both sides of the connection have functional parity; a 
TConnectedEndpoint is talking to another TConnectedEndpoint and both are able 
to service calls as well as replies.  In addition to this I made this 
thread-safe so that a single TConnectedEndpoint can be used by multiple threads 
simultaneously.  Although each call to a client function is a synchronous 
send/recv; you can have multiple threads all using the calls because the 
channel ID and sequence ID are unique and can route the reply to the correct 
waiting code.

Here is a sample of the C# test code that performs two-way channel 
communication:

A service called echo.thrift is defined:
namespace csharp Test
service EchoServer channel 3
{
  string Echo       (1:string toEcho),
  string ReverseEcho(1:string toReverse),
}

A service called rand.thrift is also defined:
namespace csharp Test
service RandServer channel 55
{
  i32 Random(),
}

The following code creates a server:

TServerSocket tServerSocket = new TServerSocket(port);
m_server = new TListeningEndpoint(tServerSocket, 255); // semaphore 255 
simultaneous
m_server.AddRequestProcessor(new EchoServer.Processor(new EchoServerImpl()));
m_server.AddRequestProcessor(new RandServer.Processor(new RandServerImpl()));
m_server.ClientConnect += new EventHandler(m_server_ClientConnect);
m_server.Start();

The following code creates the client:

TSocket originator = new TSocket("localhost", port);
m_clientEnd = new TConnectedEndpoint(originator);
m_clientEnd.AddRequestProcessor(new EchoServer.Processor(new EchoServerImpl()));
m_clientEnd.AddRequestProcessor(new RandServer.Processor(new RandServerImpl()));
m_clientEnd.Start();

When the client connects to the server, the ClientConnect event fires so that 
the test can cache the TConnectedEndpoint representing the server's end of the 
connection.

EchoServer.Client echoRequestFromClient = new EchoServer.Client(m_clientEnd);
string echoedByServer = echoRequestFromClient.Echo("echoedByServer");

That calls the Echo function from the client to the server.

EchoServer.Client echoRequestFromServer = new EchoServer.Client(m_serverEnd);
string echoedByClient = echoRequestFromServer.Echo("echoedByClient");

That calls the Echo function from the server to the client - on the same 
connection!

Comments?

James E. King, III                       300 Innovative Way, Suite 301
Senior Software Engineer                 Nashua, NH 03062
Dell (EqualLogic) HIT Team (ASM)         (603) 589-5895

Reply via email to