13

When a single ClientBase<T> instance is used for multiple WCF service calls, it can get a channel into a faulted state (ie. when the service is down).

I would like to heal the channel automatically when the service comes up again. The only way I found is to call the following code before each method call:

if (clientBase.InnerChannel.State == CommunicationState.Faulted)
{
      clientBase.Abort();
      ((IDisposable)clientBase).Dispose();
      clientBase = new SampleServiceClientBase();
}

I got the feeling that this isn't the right way to do it. Anyone got a better idea?

Jader Dias
  • 88,211
  • 155
  • 421
  • 625

2 Answers2

21

You can't. Once a channel is faulted, it's faulted for good. You must create a new channel. WCF channels are stateful (in a manner of speaking), so a faulted channel means the state may be corrupted.

What you can do is put the logic you're using into a utility method:

public static class Service<T> where T : class, ICommunicationObject, new()
{
    public static void AutoRepair(ref T co)
    {
        AutoRepair(ref co, () => new T());
    }

    public static void AutoRepair(ref T co, Func<T> createMethod)
    {
        if ((co != null) && (co.State == CommunicationState.Faulted))
        {
            co.Abort();
            co = null;
        }
        if (co == null)
        {
            co = createMethod();
        }
    }
}

Then you can invoke your service with the following:

Service<SampleServiceClient>.AutoRepair(ref service,
    () => new SampleServiceClient(someParameter));
service.SomeMethod();

Or if you want to use the default parameterless constructor, just:

Service<SampleServiceClient>.AutoRepair(ref service);
service.SomeMethod();

Since it also handles the case where the service is null, you don't need to initialize the service before calling it.

Pretty much the best I can offer. Maybe somebody else has a better way.

RobIII
  • 8,488
  • 2
  • 43
  • 93
Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • 1
    Do you need T also to implement IDisposable? – David Gardiner Aug 23 '12 at 04:30
  • @DavidGardiner: Not if it implements `ICommunicationObject`. The `Dispose` implementation on WCF channels is actually part of the problem. – Aaronaught Aug 23 '12 at 23:54
  • But the ICommunicationObject interface (http://msdn.microsoft.com/en-us/library/system.servicemodel.icommunicationobject.aspx) doesn't implement IDisposable - so the example above won't work without casting co to IDisposable. – David Gardiner Aug 24 '12 at 01:57
  • 2
    @DavidGardiner: The `Dispose` method shouldn't have been in my example, I removed it. Calling `Dispose` on communication objects is well-documented to be wrong, as it just calls `Close` internally when you actually want to call `Abort` on a fault (as above). Unless you're implementing your own communication objects, you don't need to concern yourself with `IDisposable` here. There's a more detailed example in an [interceptor implementation I wrote](https://github.com/Aaronaught/Convolved.Hosting/blob/master/Convolved.Hosting/RestartOnFaultInterceptor%601.cs) – Aaronaught Aug 24 '12 at 02:02
  • 1
    thank you. Actually co = null, throws an exception and suggests to replace null with default(co). I did uncomment that line. – SwissCoder Oct 18 '12 at 08:48
  • @SwissCoder, I'm not really sure what you mean by that; there's no way that line of code would actually throw an exception at runtime, are you referring to some guidance from fxcop or resharper? – Aaronaught Oct 18 '12 at 22:19
  • when I remember correctly, the compiler threw an error as co could be a NonNullable type. – SwissCoder Oct 19 '12 at 06:32
  • 1
    @SwissCoder: You might need to add the `class` constraint to the generic definition in that case. No need to change the code, as there are no structs that implement `ICommunicationObject`. – Aaronaught Oct 20 '12 at 00:16
  • @Aaronaught: yeah it works that way, good idea. thanks! -->code changed to: 'where T : class, ICommunicationObject, new()' – SwissCoder Oct 22 '12 at 05:05
  • I like your implementation but shouldn't you protect against exceptions when aborting the channel? (based on http://stackoverflow.com/questions/1241331/recovering-from-a-communicationobjectfaultedexception-in-wcf) – jbatista Mar 20 '15 at 10:59
  • @jbatista: Exceptions aren't supposed to happen on `Abort`. Only `Close` has that behavior. – Aaronaught Mar 21 '15 at 03:38
  • Has anyone tried to integrate this approach with MEF? I don't like the idea of passing the client channel around everywhere and I don't like a static copy either. IOC seems to be a good way to make a single channel available to different depths of libraries, but the fact that a faulted channel can't be repaired makes it difficult to use a container. – Quark Soup May 19 '16 at 18:17
1

This is what I'm currently doing, but I can't say this is the best option either.

I recreate the proxy when an exception is caught on the call.

try
{
    ListCurrentProcesses();
}
catch (TypeLoadException ex)
{
    Debug.Print("Oops: " + ex.Message);
    m_Proxy = new ProcessManagerProxy();
}
catch (EndpointNotFoundException endpointEX)
{
    Debug.Print("Oops: " + endpointEX.Message);
    m_Proxy = new ProcessManagerProxy();
}
catch (CommunicationException communicationEx)
{
    Debug.Print("Oops: " + communicationEx.Message);
    m_Proxy = new ProcessManagerProxy();
}
Scott P
  • 3,775
  • 1
  • 24
  • 30
  • Never catch `SystemException`, **especially** if you're not re-throwing. That tree includes instances like `OutOfMemoryException` and `StackOverflowException`. Also, you're not properly disposing of the old channel here. – Aaronaught Jan 05 '10 at 19:51
  • Understood. That was just a quick example. Do you see a big problem letting the channel get collected instead of explicitly disposing it? I'm assuming we are not going to retry this operation 1000 times before we give up. – Scott P Jan 05 '10 at 19:54
  • 2
    @Scott P: The `ClientBase` class does not seem to have a finalizer on it that would invoke `Close` or `Abort`, so I would say, yes, it is a problem if you never call `Dispose` (or `Close` or `Abort`); it's probably leaking network resources. – Aaronaught Jan 05 '10 at 21:55