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