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