Click here to Skip to main content
15,179,246 members
Articles / Programming Languages / C# 5.0
Tip/Trick
Posted 16 Oct 2014

Tagged as

Stats

20.7K views
102 downloads
11 bookmarked

How to Create WCF Timeout Operation Behavior

Rate me:
Please Sign up or sign in to vote.
4.88/5 (5 votes)
16 Oct 2014CPOL4 min read
Just one way to solve a common problem of WCF timing out

Introduction

WCF acts up when it is under heavy requests, the weird behavior is shown as high CP utilization accompanied with very high memory consumption and delayed responses that continuously increase till another problem happens (OutOfMemoryException, TimeoutException, etc.)

If the client is a web application, the web page will wait for some time (110 milliseconds in .NET 4.0) and then times out, keeping the service running and eating up server resources.

Whilst there are timeout configurations for the WCF client (such as OpenTimeout, CloseTimeout, SendTimeout, ReceiveTimeout), there is no configuration for the server time-out.

One way to do that is to create a behavior that can be added to the WCF operation and that controls the time they consume to complete their jobs by interrupting them if they exceed the given time.

The Solution

The solution would be if we can intercept the call and check how long it takes to complete, and cancel it if it takes more than specified configured time.

What I thought of is having IOperationBehavior to do that for me utilizing the IOperationInvoker, to take control over the invocation.

As for the asynchronous calls, I would not alter them; let them continue in the same original way.
My changes are to the synchronous calls, each call I am starting a new task to execute it, while I watch the elapsed time, if it exceeds a certain time, it will get interrupted and end the whole operation.

Using the Code

The most important part of the code is how to make a task time out after a certain point of time.

For me, this was implemented by the following:

C#
public class TimeoutOperationInvoker : IOperationInvoker
   {
       IOperationInvoker originalInvoker;
 
       public TimeoutOperationInvoker(IOperationInvoker invoker)
       { originalInvoker = invoker; }
       public object[] AllocateInputs()
       {
           return originalInvoker.AllocateInputs();
       }
 
       public object Invoke(object instance, object[] inputs, out object[] outputs)
       {
           var waitTolkenSource = new CancellationTokenSource();
           var tolkenSource = new CancellationTokenSource();
           object[] objectOutputParameter = outputs = null;
           Func<object> x = delegate() { return originalInvoker.Invoke(instance, inputs, out  objectOutputParameter); };
           var WcfTimeoutInMilliseconds = (int)(WcfTestService.Properties.Settings.Default.WcfTimeoutInSeconds * 1000);
           var mainTask = Task.Factory.StartNew(() =>
            {
               tolkenSource.CancelAfter(WcfTimeoutInMilliseconds);
 
                var response = x.Invoke();
                waitTolkenSource.Cancel();
                tolkenSource.Token.ThrowIfCancellationRequested();
                return response;
 
            }, tolkenSource.Token);
 
           var waitTask = Task.Factory.StartNew(() =>
           {
               var cancelled = waitTolkenSource.Token.WaitHandle.WaitOne(WcfTimeoutInMilliseconds);
               if (mainTask.Status == TaskStatus.Running)
               {
                   tolkenSource.Cancel();
               }
               return string.Empty;
           }, waitTolkenSource.Token);
 
 
           waitTolkenSource.Token.WaitHandle.WaitOne(WcfTimeoutInMilliseconds );
           Task.WaitAny(mainTask,waitTask);//
           if (mainTask.Status.HasFlag(TaskStatus.RanToCompletion))
           {
               var result = mainTask.Result;
               outputs = objectOutputParameter;
               return result;
           }
           else
           {
               tolkenSource.Token.ThrowIfCancellationRequested();
               throw mainTask.Exception;
           } 
       }
 
       public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
       {
           return this.originalInvoker.InvokeBegin(instance, inputs, callback, state);
       }
 
       public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
       {
           return this.originalInvoker.InvokeEnd(instance, out outputs, result);
       }
 
       public bool IsSynchronous
       {
           get { return this.originalInvoker.IsSynchronous; }
       }
   }

Now, let's see the actual invoker that does nothing but overriding the synchronous requests, because asynchronous ones won't suffer from the time out issue.

C#
public class WcfTimeoutOperationBehavior : Attribute, IOperationBehavior
    {
        public void AddBindingParameters(OperationDescription operationDescription, 
        System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {
 
        }
 
        public void ApplyClientBehavior(OperationDescription operationDescription, 
        System.ServiceModel.Dispatcher.ClientOperation clientOperation)
        {
 
        }
 
        public void ApplyDispatchBehavior(OperationDescription operationDescription, 
        System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation)
        {       
            dispatchOperation.Invoker = new TimeoutOperationInvoker(dispatchOperation.Invoker); 
        }
 
        public void Validate(OperationDescription operationDescription)
        {
 
        }

The service helper is very important on the client side, the purpose of it is to make sure that the client (proxy) won't suffer because the service hangs and stops responding when the channel is faulted. The service helper would be like this (also check the references at the end of this tip).

C#
    public static void Using<TService>(Action<TService> action)
        where TService : ICommunicationObject, IDisposable, new()
    {
        var service = new TService();
        bool success = false;
        try
        {
            action(service);
            if (service.State != CommunicationState.Faulted)
            {
                service.Close();
                success = true;
            }
        }
        finally
        {
            if (!success)
            {
                service.Abort();
            }
        }
    }
}

The self hosted service and the client can be like this. You can run your code to test it.

C#
static void Main(string[] args)
       {
           using (var host = new ServiceHost(typeof(WcfTestService.Service1)))
           {
               host.Open();
 
               int input = 0;
               var sw = new System.Diagnostics.Stopwatch();
               while (true)
               {
                   string response = null;
                   sw.Start();
                   Console.WriteLine("Input is :{0}", input);
                   try
                   { 
                       ServiceHelper.Using<wcfclient.client.service1client>(client =>
                           {
                               response = client.GetData(input);//
                               Console.WriteLine("Response {0}", response);
                           });
                   }
                   catch (FaultException<exceptiondetail> ex)
                   {
                       var tmp = Console.ForegroundColor;
                       Console.ForegroundColor = ConsoleColor.Red;
                       if (null == ex.Detail.InnerException) Console.WriteLine(ex.Message);
                       else Console.WriteLine(ex.Detail.InnerException.Message);
                       Console.ForegroundColor = tmp;
                   }
                   finally
                   {
                       input += 100;
                       Console.WriteLine("Response took {0} milliseconds", sw.ElapsedMilliseconds);
                       sw.Reset();
                   }
 
                   if (Console.ReadKey().Key == ConsoleKey.Escape)
                       break;
               }
               Console.WriteLine("Application is terminating, Please wait...");
               host.Close();
           }
       }

Finally the configurations:

XML
<system.servicemodel>
    <services>
      <service name="WcfTestService.Service1">
        <host>
          <baseAddresses>
            <add baseaddress="http://localhost/WcfTestService">
          </baseAddresses>
        </add></host>
        <endpoint contract="WcfTestService.IService1" binding="basicHttpBinding" />
      </service>
    </services>
    <behaviors>
      <servicebehaviors>
        <behavior>
          <servicemetadata httpsgetenabled="true" httpgetenabled="true">
          <servicedebug includeexceptiondetailinfaults="true">
        </servicedebug></servicemetadata></behavior>
      </servicebehaviors>
    </behaviors>

Concerns and Alternative

The sample shows the basic idea of aborting the call when it exceeds a certain period of time; it will need some enhancements and many tests to see how it works under heavy loads and different requests in real production scenarios.

One of my major concerns was that concept depends on TPL and it will create 2 more threads of executions added to the main calling thread, and that happens for each and every call, which is a major concern in terms of impacting the server’s memory and also which questions the overhead that this solution would bring along.

It is a common WCF problem that faulted channel will hand and stop to respond. That is where the solution provided by Damien McGivern (see the reference below) comes in the scene as the sought solution for this problem.

A much simpler way to avoid that overhead is to embark the OperationContext to manage the state of the service or the operation to be more specific and check the time it is taking multiple times in its invocation, this solution works great without any impact or overheads that threads would add, but it will not work if you can’t break in the call to multiple lines of code in which you can check the timeout.

Unfortunately, we can’t apply operation behaviors in the configuration file, unlike service and endpoint ones. Those can be controlled from the configuration by extending BehaviorExtensionElement, the reason is that there are explicit configurations for all the services and all the endpoints of each but not for any of their operations where you can apply extensions.

The Result

Here you can see the result. The client will send requests, each request will be delayed 100 milliseconds and the timeout configuration is 1 second. Once the response takes 1000 milliseconds or more, it will immediately abort and return without any delay, saving the server resources.

Image 1

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Assil
Architect
United States United States
I graduated as an electronic engineer at 2000, and I have been working in software development ever since.
Interested mainly in .NET technologies.

Comments and Discussions

 
QuestionNice article Pin
Dominic Burford22-Oct-14 5:53
professionalDominic Burford22-Oct-14 5:53 
AnswerRe: Nice article Pin
Assil22-Oct-14 5:56
professionalAssil22-Oct-14 5:56 
QuestionI dont get this code Pin
Sacha Barber17-Oct-14 4:02
MemberSacha Barber17-Oct-14 4:02 
AnswerRe: I dont get this code Pin
Assil17-Oct-14 4:33
professionalAssil17-Oct-14 4:33 
AnswerRe: I dont get this code Pin
Assil17-Oct-14 4:42
professionalAssil17-Oct-14 4:42 
QuestionFormatting Pin
OriginalGriff16-Oct-14 20:10
mveOriginalGriff16-Oct-14 20:10 
AnswerRe: Formatting Pin
Assil18-Oct-14 12:28
professionalAssil18-Oct-14 12:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.