49

I'm attempting to extend this answer on SO to make a WCF client retry on transient network failures and handle other situations that require a retry such as authentication expiration.

Question:

What are the WCF exceptions that need to be handled, and what is the correct way to handle them?

Here are a few sample techniques that I'm hoping to see instead of or in addition to proxy.abort():

  • Delay X seconds prior to retry
  • Close and recreate a New() WCF client. Dispose the old one.
  • Don't retry and rethrow this error
  • Retry N times, then throw

Since it's unlikely one person knows all the exceptions or ways to resolve them, do share what you know. I'll aggregate the answers and approaches in the code sample below.

 // USAGE SAMPLE //int newOrderId = 0; // need a value for definite assignment //Service<IOrderService>.Use(orderService=> //{ // newOrderId = orderService.PlaceOrder(request); //} /// <summary> /// A safe WCF Proxy suitable when sessionmode=false /// </summary> /// <param name="codeBlock"></param> public static void Use(UseServiceDelegateVoid<T> codeBlock) { IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); bool success = false; try { codeBlock((T)proxy); proxy.Close(); success = true; } catch (CommunicationObjectAbortedException e) { // Object should be discarded if this is reached. // Debugging discovered the following exception here: // "Connection can not be established because it has been aborted" throw e; } catch (CommunicationObjectFaultedException e) { throw e; } catch (MessageSecurityException e) { throw e; } catch (ChannelTerminatedException) { proxy.Abort(); // Possibly retry? } catch (ServerTooBusyException) { proxy.Abort(); // Possibly retry? } catch (EndpointNotFoundException) { proxy.Abort(); // Possibly retry? } catch (FaultException) { proxy.Abort(); } catch (CommunicationException) { proxy.Abort(); } catch (TimeoutException) { // Sample error found during debug: // The message could not be transferred within the allotted timeout of // 00:01:00. There was no space available in the reliable channel's // transfer window. The time allotted to this operation may have been a // portion of a longer timeout. proxy.Abort(); } catch (ObjectDisposedException ) { //todo: handle this duplex callback exception. Occurs when client disappears. // Source: https://stackoverflow.com/questions/1427926/detecting-client-death-in-wcf-duplex-contracts/1428238#1428238 } finally { if (!success) { proxy.Abort(); } } } 
1
  • 2
    For Petes sake, please get rid of the e in the throw e in those catch blocks. It throws away the entire stack trace before it, and makes logical troubleshooting into a guessing game. Commented Mar 11, 2020 at 2:29

4 Answers 4

23

EDIT: There seems to be some inefficiencies with closing and reopening the client multiple times. I'm exploring solutions here and will update & expand this code if one is found. (or if David Khaykin posts an answer I'll mark it as accepted)

After tinkering around with this for a few years, the code below is my preferred strategy (after seeing this blog posting from the wayback machine) for dealing with WCF retries and handling exceptions.

I investigated every exception, what I would want to do with that exception, and noticed a common trait; every exception that needed a "retry" inherited from a common base class. I also noticed that every permFail exception that put the client into an invalid state also came from a shared base class.

The following example traps every WCF exception a client could through, and is extensible for your own custom channel errors.

Sample WCF Client Usage

Once you generate your client side proxy, this is all you need to implement it.

Service<IOrderService>.Use(orderService=> { orderService.PlaceOrder(request); } 

ServiceDelegate.cs

Add this file to your solution. No changes are needed to this file, unless you want to alter the number of retries or what exceptions you want to handle.

public delegate void UseServiceDelegate<T>(T proxy); public static class Service<T> { public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); public static void Use(UseServiceDelegate<T> codeBlock) { IClientChannel proxy = null; bool success = false; Exception mostRecentEx = null; int millsecondsToSleep = 1000; for(int i=0; i<5; i++) // Attempt a maximum of 5 times { // Proxy cann't be reused proxy = (IClientChannel)_channelFactory.CreateChannel(); try { codeBlock((T)proxy); proxy.Close(); success = true; break; } catch (FaultException customFaultEx) { mostRecentEx = customFaultEx; proxy.Abort(); // Custom resolution for this app-level exception Thread.Sleep(millsecondsToSleep * (i + 1)); } // The following is typically thrown on the client when a channel is terminated due to the server closing the connection. catch (ChannelTerminatedException cte) { mostRecentEx = cte; proxy.Abort(); // delay (backoff) and retry Thread.Sleep(millsecondsToSleep * (i + 1)); } // The following is thrown when a remote endpoint could not be found or reached. The endpoint may not be found or // reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable. catch (EndpointNotFoundException enfe) { mostRecentEx = enfe; proxy.Abort(); // delay (backoff) and retry Thread.Sleep(millsecondsToSleep * (i + 1)); } // The following exception that is thrown when a server is too busy to accept a message. catch (ServerTooBusyException stbe) { mostRecentEx = stbe; proxy.Abort(); // delay (backoff) and retry Thread.Sleep(millsecondsToSleep * (i + 1)); } catch (TimeoutException timeoutEx) { mostRecentEx = timeoutEx; proxy.Abort(); // delay (backoff) and retry Thread.Sleep(millsecondsToSleep * (i + 1)); } catch (CommunicationException comException) { mostRecentEx = comException; proxy.Abort(); // delay (backoff) and retry Thread.Sleep(millsecondsToSleep * (i + 1)); } catch(Exception e) { // rethrow any other exception not defined here // You may want to define a custom Exception class to pass information such as failure count, and failure type proxy.Abort(); throw e; } } if (success == false && mostRecentEx != null) { proxy.Abort(); throw new Exception("WCF call failed after 5 retries.", mostRecentEx ); } } } 
Sign up to request clarification or add additional context in comments.

15 Comments

I have the same question you do. Is that the approach you have used? And for calls that are gong to take a while (2-20 seconds) what pattern should be used there.
In general I've been using .NET 4 cancellation token to issue timeouts to my code, but haven't yet hooked it into WCF. MSDN has a good sample on cancellation tokens.
@themakerofthings7, the order matters: in my understanding we have to catch FaultException first (as this is logic-related), then CommunicationException as a cause to retry, then TimeoutException, then Excpetion to catch all remaining. Unless I missed something else.
Why is the ChannelFactory<T> not disposed of? Is this not necessary anymore or is it just not part of the example code?
We've seen one more WCF exception, ProtocolException. It occurs when the service on the other side for example stops negotiating HTTPS correctly, but otherwise connects.
|
4

I started a project on Codeplex that has the following features

  • Allows efficient reuse of the client proxy
  • Cleans up all resources, including EventHandlers
  • Operates on Duplex channels
  • Operates on Per-call services
  • Supports config constructor, or by factory

http://smartwcfclient.codeplex.com/

It is a work in progress, and is very heavily commented. I'll appreciate any feedback regarding improving it.

Sample usage when in instance mode:

 var reusableSW = new LC.Utils.WCF.ServiceWrapper<IProcessDataDuplex>(channelFactory); reusableSW.Reuse(client => { client.CheckIn(count.ToString()); }); reusableSW.Dispose(); 

Comments

2

we have a WCF client that deal with almost any type of failure at the server. The Catch list is very long but does not have to be. If you look closely, you will see that many exceptions are child definitions of the Exception Class (and a few other classes).

Thus you can simplify things a lot if you want to. That said, here are some typical errors that we catch:

Server timeout
Server too busy
Server unavailable.

2 Comments

This is old, but if I recall correctly, we created our own exception classes in that project, that was a subclass of the exception class
Sorry. no. This was over 6 years ago
2

Below links may help to handle WCF Exceptions:

http://www.codeproject.com/KB/WCF/WCFErrorHandling.aspx

http://msdn.microsoft.com/en-us/library/cc949036.aspx

1 Comment

+1 - Great links. Can you assist (perhaps by editing your answer) with a text description of how I can use IExceptionToFaultConverter or the other classes to actually implement corrective WCF behavior for timeouts, etc?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.