Routing Service Features - Error Handling Part 1

Today we continue the set of articles on the core features of the RoutingService.  In this post, I'll explain Error Handling, a great feature that provides value to both common an high-end uses of the Routing Service by providing a way to make your application more tolerant of temporary communication and network errors.

First: what do we mean when we say errors?  In this case, we're talking primarily about CommunicationExceptions and TimeoutExceptions, two classes of Exception that (can) occur commonly in a distributed system.  Communication Exceptions are things like EndpointNotFoundExceptions that generally indicate that the service that you were trying to talk to was down, didn't provide a correct response, or didn’t understand the message you sent it.  TimeoutExceptions are just what they sound like: you've been stuck waiting for a response for too long.  Either of these could happen for a variety of reasons: temporary network outage, messages that get lost on the network, glitches or bugs in the configuration of the services involved, implementation issues of the remote service, etc.

Since the Routing Service is acting on behalf of the client, we will sometimes see these errors.  Now, we could just forward these exceptions back to the client and let the client application deal with them.  However, since we're already participating in the message flow, there's an opportunity for us to do something else, which can help your application be more robust and tolerant of these common (and frequently temporary) errors.  We allow you to configure a set of alternate endpoints that the Routing Service should use as backups when a particular endpoint returns an exception.  In these cases the Routing Service will "fail-over" to the backup endpoint and try to deliver your message again.

ErrorHandling1

Aside from a slightly slower response (after all, the Routing Service does need to wait for the exception to occur before taking action), the client is none the wiser. 

As we’ve seen before, when defining an entry in the MessageFilterTable, I normally map a MessageFilter to a particular destination endpoint like this:

<add filterName="someFilter1" endpointName="someDestinationEndpoint1" />

When defining backup endpoints, this changes slightly, as we have to both define the backup list, and then reference it in the filter table entry. First, to create the backup list, we define a new section inside of the <routing> section called, appropriately “backupLists”.  This is where we create a particular list and add endpoints to it. 

 

<backupLists>

     <backupList name="backupList1">

          <add endpointName="backup">

          <add endpointName="backup2">

     </backupList>

</backupLists>

 

To actually use the list of alternates that we built, add a reference to the list to the filter table entry, like so:

 

<add filterName="someFilter1" endpointName="someDestinationEndpoint1" backupList="backupList1"/>

 

In code, setting up backups would look like this:

 

//assume the endpoint objects and the filter objects already exist

      List<ServiceEndpoint> endpointList = new List<ServiceEndpoint>(){someDestinationEndpoint1, backup, backup2};

RoutingConfiguration rc = new RoutingConfiguration();

      rc.FilterTable.Add(someFilter1, endpointList);

The order that the endpoints are added to the list are the order in which they will be attempted.  With this configuration, first the endpoint referenced by the endpointName would be tried.  Should the connection to that endpoint fail or time out, the first endpoint in the backup list (“backup”) would be tried.  Only if communication to that destination failed would the message be sent to backup2.  Should backup2 fail, the client would get the same exception/error they normally would have had there been no alternates specified.  In this way we add reliability without changing the underlying model expected by the client. 

 

Error handling can be added to pretty much any messaging pattern or protocol: one-way, request response, duplex, multicast, sessionful, etc.  The logic present knows how to handle everything from single-message request-response messages (basicHttp) to sessionful, transactional messages (MSMQ).  These more advanced error handling semantics will be the topic of the next post, since talking through their state can be somewhat tricky.

 

Enjoy!

-Matt

Comments

  • Anonymous
    August 09, 2010
    Hi I developed routing service for round robin routing. I added backup lists to this routing service in round robin pattern in config file. My configuration is as below. <services>      <service behaviorConfiguration="routingConfiguration" name="System.ServiceModel.Routing.RoutingService">        <host>          <baseAddresses>            <add baseAddress="net.tcp://localhost/FDPRoutingService/router"/>          </baseAddresses>        </host>        <!--Set up the inbound endpoints for the Routing Service-->        <!--first create the general router endpoint-->        <endpoint address="" binding="netTcpBinding"                  name="NetTcpBinding_IFDService" contract="System.ServiceModel.Routing.ISimplexDatagramRouter"/>      </service>    </services>    <behaviors>      <!--default routing service behavior definition-->      <serviceBehaviors>        <behavior name="routingConfiguration">          <routing filterTableName="filterTable1" routeOnHeadersOnly="false"/>          <serviceDebug includeExceptionDetailInFaults="true"/>        </behavior>      </serviceBehaviors>    </behaviors>    <bindings>      <netMsmqBinding>        <binding name="NetMsmqBinding_IFDService" closeTimeout="00:01:00"          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"          deadLetterQueue="System" durable="true" exactlyOnce="true" maxReceivedMessageSize="65536"          maxRetryCycles="2" receiveErrorHandling="Fault" receiveRetryCount="5"          retryCycleDelay="00:30:00" timeToLive="1.00:00:00" useSourceJournal="false"          useMsmqTracing="false" queueTransferProtocol="Native" maxBufferPoolSize="524288"          useActiveDirectory="false">          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"            maxBytesPerRead="4096" maxNameTableCharCount="16384" />          <security mode="None">            <transport msmqAuthenticationMode="WindowsDomain" msmqEncryptionAlgorithm="RC4Stream"              msmqProtectionLevel="Sign" msmqSecureHashAlgorithm="Sha1" />            <message clientCredentialType="Windows" />          </security>        </binding>        <binding name="FDService_EP" closeTimeout="00:01:00" openTimeout="00:01:00"          receiveTimeout="00:10:00" sendTimeout="00:01:00" deadLetterQueue="System"          durable="true" exactlyOnce="true" maxReceivedMessageSize="65536"          maxRetryCycles="2" receiveErrorHandling="Fault" receiveRetryCount="5"          retryCycleDelay="00:30:00" timeToLive="1.00:00:00" useSourceJournal="false"          useMsmqTracing="false" queueTransferProtocol="Native" maxBufferPoolSize="524288"          useActiveDirectory="false">          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"            maxBytesPerRead="4096" maxNameTableCharCount="16384" />          <security mode="None">            <transport msmqAuthenticationMode="WindowsDomain" msmqEncryptionAlgorithm="RC4Stream"              msmqProtectionLevel="Sign" msmqSecureHashAlgorithm="Sha1" />            <message clientCredentialType="Windows" />          </security>        </binding>      </netMsmqBinding>      <netTcpBinding>        <binding name="NetTcpBinding_IFDService" closeTimeout="00:01:00"          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"          maxBufferPoolSize="524288" maxReceivedMessageSize="65536">          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"            maxBytesPerRead="4096" maxNameTableCharCount="16384" />          <security mode="None">            <message clientCredentialType="Windows" />          </security>        </binding>      </netTcpBinding>    </bindings>    <client>      <endpoint address="net.msmq://localserver/private/FDMessageQueue"        binding="netMsmqBinding" bindingConfiguration="NetMsmqBinding_IFDService"        contract="" name="localFDPQueue1" />      <endpoint address="net.msmq://localserver/private/FDMessageQueue02"        binding="netMsmqBinding" bindingConfiguration="NetMsmqBinding_IFDService"        contract="" name="localFDPQueue2" />      <endpoint address="net.msmq://localserver/private/FDMessageQueue03"        binding="netMsmqBinding" bindingConfiguration="NetMsmqBinding_IFDService"        contract="" name="localFDPQueue3" />      <endpoint address="net.msmq://remoteserver/private/fdmessagequeue"        binding="netMsmqBinding" bindingConfiguration="NetMsmqBinding_IFDService"        contract="" name="devFDPQueue1" />      <endpoint address="net.msmq://remoteserver/private/fdmessagequeue_01"        binding="netMsmqBinding" bindingConfiguration="NetMsmqBinding_IFDService"        contract="" name="devFDPQueue2" />      <endpoint address="net.msmq://remoteserver/private/fdmessagequeue_02"        binding="netMsmqBinding" bindingConfiguration="NetMsmqBinding_IFDService"        contract="" name="devFDPQueue3" />      <endpoint address="net.msmq://remoteserver/private/fdmessagequeue_03"        binding="netMsmqBinding" bindingConfiguration="NetMsmqBinding_IFDService"        contract="" name="devFDPQueue4" />      <endpoint address="net.msmq://remoteserver/private/fdmessagequeue_04"        binding="netMsmqBinding" bindingConfiguration="NetMsmqBinding_IFDService"        contract="" name="devFDPQueue5" />      <endpoint address="net.msmq://remoteserver/private/fdmessagequeue_05"        binding="netMsmqBinding" bindingConfiguration="NetMsmqBinding_IFDService"        contract="*" name="devFDPQueue6" />    </client>    <routing>      <backupLists>        <backupList name ="backuplocalFDPQueue1">          <add endpointName="devFDPQueue1"/>          <add endpointName="localFDPQueue2"/>          <add endpointName="devFDPQueue2"/>          <add endpointName="localFDPQueue3"/>          <add endpointName="devFDPQueue3"/>          <add endpointName="devFDPQueue4"/>          <add endpointName="devFDPQueue5"/>          <add endpointName="devFDPQueue6"/>        </backupList>        <backupList name ="backuplocalFDPQueue2">          <add endpointName="devFDPQueue2"/>          <add endpointName="localFDPQueue3"/>          <add endpointName="devFDPQueue3"/>          <add endpointName="localFDPQueue1"/>          <add endpointName="devFDPQueue4"/>          <add endpointName="devFDPQueue5"/>          <add endpointName="devFDPQueue6"/>          <add endpointName="devFDPQueue1"/>        </backupList>        <backupList name="backuplocalFDPQueue3">          <add endpointName="devFDPQueue3"/>          <add endpointName="localFDPQueue1"/>          <add endpointName="devFDPQueue4"/>          <add endpointName="localFDPQueue2"/>          <add endpointName="devFDPQueue5"/>          <add endpointName="devFDPQueue6"/>          <add endpointName="devFDPQueue1"/>          <add endpointName="devFDPQueue2"/>        </backupList>        <backupList name="backupdevFDPQueue1">          <add endpointName="localFDPQueue2"/>          <add endpointName="devFDPQueue2"/>          <add endpointName="localFDPQueue3"/>          <add endpointName="devFDPQueue3"/>          <add endpointName="localFDPQueue1"/>          <add endpointName="devFDPQueue4"/>          <add endpointName="devFDPQueue5"/>          <add endpointName="devFDPQueue6"/>        </backupList>        <backupList name="backupdevFDPQueue2">          <add endpointName="localFDPQueue3"/>          <add endpointName="devFDPQueue3"/>          <add endpointName="localFDPQueue1"/>          <add endpointName="devFDPQueue4"/>          <add endpointName="localFDPQueue2"/>          <add endpointName="devFDPQueue5"/>          <add endpointName="devFDPQueue6"/>          <add endpointName="devFDPQueue1"/>        </backupList>        <backupList name="backupdevFDPQueue3">          <add endpointName="localFDPQueue1"/>          <add endpointName="devFDPQueue4"/>          <add endpointName="localFDPQueue2"/>          <add endpointName="devFDPQueue5"/>          <add endpointName="localFDPQueue3"/>          <add endpointName="devFDPQueue6"/>          <add endpointName="devFDPQueue1"/>          <add endpointName="devFDPQueue2"/>        </backupList>        <backupList name="backupdevFDPQueue4">          <add endpointName="localFDPQueue1"/>          <add endpointName="devFDPQueue5"/>          <add endpointName="localFDPQueue2"/>          <add endpointName="devFDPQueue6"/>          <add endpointName="localFDPQueue3"/>          <add endpointName="devFDPQueue1"/>          <add endpointName="devFDPQueue2"/>          <add endpointName="devFDPQueue3"/>        </backupList>        <backupList name="backupdevFDPQueue5">          <add endpointName="localFDPQueue1"/>          <add endpointName="devFDPQueue6"/>          <add endpointName="localFDPQueue2"/>          <add endpointName="devFDPQueue1"/>          <add endpointName="localFDPQueue3"/>          <add endpointName="devFDPQueue2"/>          <add endpointName="devFDPQueue3"/>          <add endpointName="devFDPQueue4"/>        </backupList>        <backupList name="backupdevFDPQueue6">          <add endpointName="localFDPQueue1"/>          <add endpointName="devFDPQueue1"/>          <add endpointName="localFDPQueue2"/>          <add endpointName="devFDPQueue2"/>          <add endpointName="localFDPQueue3"/>          <add endpointName="devFDPQueue3"/>          <add endpointName="devFDPQueue4"/>          <add endpointName="devFDPQueue5"/>        </backupList>      </backupLists>      <filters>        <!--Set up the custom message filters.  In this example, we'll use the example round robin message filter, which alternates between the references-->        <filter name="RoundRobinFilter1" filterType="Custom" customType="Teletrac.Customer.Services.FDPRoutingService.RoundRobinMessageFilter, Teletrac.Customer.Services.FDPRoutingService" filterData="FDP1"/>        <filter name="RoundRobinFilter2" filterType="Custom" customType="Teletrac.Customer.Services.FDPRoutingService.RoundRobinMessageFilter, Teletrac.Customer.Services.FDPRoutingService" filterData="FDP1"/>        <filter name="RoundRobinFilter3" filterType="Custom" customType="Teletrac.Customer.Services.FDPRoutingService.RoundRobinMessageFilter, Teletrac.Customer.Services.FDPRoutingService" filterData="FDP1"/>        <filter name="RoundRobinFilter4" filterType="Custom" customType="Teletrac.Customer.Services.FDPRoutingService.RoundRobinMessageFilter, Teletrac.Customer.Services.FDPRoutingService" filterData="FDP2"/>        <filter name="RoundRobinFilter5" filterType="Custom" customType="Teletrac.Customer.Services.FDPRoutingService.RoundRobinMessageFilter, Teletrac.Customer.Services.FDPRoutingService" filterData="FDP2"/>        <filter name="RoundRobinFilter6" filterType="Custom" customType="Teletrac.Customer.Services.FDPRoutingService.RoundRobinMessageFilter, Teletrac.Customer.Services.FDPRoutingService" filterData="FDP2"/>        <filter name="RoundRobinFilter7" filterType="Custom" customType="Teletrac.Customer.Services.FDPRoutingService.RoundRobinMessageFilter, Teletrac.Customer.Services.FDPRoutingService" filterData="FDP2"/>        <filter name="RoundRobinFilter8" filterType="Custom" customType="Teletrac.Customer.Services.FDPRoutingService.RoundRobinMessageFilter, Teletrac.Customer.Services.FDPRoutingService" filterData="FDP2"/>        <filter name="RoundRobinFilter9" filterType="Custom" customType="Teletrac.Customer.Services.FDPRoutingService.RoundRobinMessageFilter, Teletrac.Customer.Services.FDPRoutingService" filterData="FDP2"/>      </filters>      <filterTables>        <filterTable name="filterTable1">          <!--Round robin requests between the various end points (services and queues)-->          <add filterName="RoundRobinFilter1" endpointName="localFDPQueue1" backupList="backuplocalFDPQueue1" />          <!--FDP1-->          <add filterName="RoundRobinFilter2" endpointName="localFDPQueue2" backupList="backuplocalFDPQueue2" />          <!--FDP1-->          <add filterName="RoundRobinFilter3" endpointName="localFD