Saturday, April 1, 2017

Advanced concepts WCF listener for Service Bus


For basics of creating a WCF listener to a ServiceBus queue/topic, Pl refer -
WCF-ServiceBus Listener




1)      The default protocol used is NetMessaging (not AMQP). NetMessaging is a native protocol available in .NET only. AMQP is an OASIS standard and interoperable, meaning message sent by .NET publisher could by consumed by PHP/Python client.
2)      Unlike Service Bus, ActiveMQ offers a lot of protocols for message interchange like –
a)      Openwire (native protocol for activemq – default)
b)      Mqtt (light weight pub-sub protocol typically used for IoT)
c)      Amqp (cross platform, efficient)
d)  STOMP
3)      With the above approach, a new instance of WCF service is created each time, a message arrives the topic/queue. This was confirmed with the following code –
public interface IService
{       
        [OperationContract(IsOneWay = true, Action = "*"), ReceiveContextEnabled(ManualControl = true)]
        void MessageReader(Message message);
}
public class MyService: IService
{
        private string instanceId = "";
        public MyService()
        {
            instanceId = Guid.NewGuid().ToString();           
        }
        public void MessageReader(Message message)
        {
            var logEntry = new LogEntry { Message = string.Format("Service instance id is {0}",instanceId) };
            Utilities.WriteLogSimple(logEntry);
        }
}

4)      Once message arrives a topic/queue, a lock is applied. All topics/queues have a message lock timeout property, whose default value is 1 min and can be set to 5 mins as maximum value. The processing of the message has to be committed (or rolled back)  within this time or else the lock will be automatically released and the message will go back to the topic/queue, thus increasing the delivery count. Pl note that this redelivery will trigger the wcf processing again .The max delivery count can also be set. Once the message has been redelivered more than the “max delivery count”, it goes to “deadletter queue”.



  5)   With reference to the point above, the following situation can occur and should be handled carefully. Suppose a message arrives a topic, a wcf instance is created and it applies the lock on the message. The processing is message takes more than 1 minute (default lock timeout) and get redelivered to the topic/queue 10 times (default max delivery count). Ultimately all the 10 times, the message gets processed, thus causing inconsistent results. So make sure the message processing timeout (say database execution timeout or service call) is always less than message lock timeout. Also reduce the max delivery count from 10 (default) to a suitable value.




Thursday, March 16, 2017

Transient fault handling and retry strategy



Transient fault handling and retry strategy

While working with webservices in the middleware layer (say a subscriber to a service bus queue), it is wise to use a good retry strategy for transient fault handling for scenarios such as – connectivity issues, timeout issues etc. Retrying immediately will not help much as the chances of the resource being unavailable would be high. The code below explains ExponentialBackoff strategy. Exponentialbackoff retry strategy means - It ll retry after 2seconds, 4seconds, 8seconds.  

Explanation of ExponentialBackoff parameters –

var retryStrategy = new ExponentialBackoff(3, TimeSpan.FromSeconds(2),
                    TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(1));
a)      first param - number of retries
b)      second param - minimum backoff time (say 2seconds). Meaning - time it should wait 2 seconds before the first RETRY or minimum time between any retries.
c)      third param - maximum backoff limit.  In this case - the third retry will happen after about ~ 2 + 4 + 8 = 14 seconds. The third param is there to put a limit on backoff period (which is 20 in this case).
d)      fourth param - delta to add some randomness, otherwise all clients would be retrying simultaenously.
in this case random value is 1 second. So 2,4,8 could be 1,5,7 etc.


Please find the code files and detailed steps below –


1)      Add a nuget package to EnterpriseLibrary.TransientFaultHandling.
2)      Create the following class (to specify errors/exceptions which should be retried) –
public class TransientErrorDetectionStrategy : ITransientErrorDetectionStrategy
    {
        // add the list of exceptions, that you consider transient.
        public bool IsTransient(Exception ex)
        {
            if (ex is WebException)
                return true;
            if (ex is FakeTimeoutException)
                return true;
            return false;
        }
    }

3)      Create a FakeTimeoutException (for demo/testing purpose) –
public class FakeTimeoutException : Exception
    {
        public FakeTimeoutException(string msg) : base(msg)
        {

        }
    }

4)      Create the main class and method as shown below –

class Program
    {
        static void Main(string[] args)
        {
            var retryStrategy = new ExponentialBackoff(3, TimeSpan.FromSeconds(2),
                    TimeSpan.FromSeconds(20), TimeSpan.FromSeconds(1));
            TransientErrorDetectionStrategy errorDetectionStrategy = new TransientErrorDetectionStrategy();

            var retryPolicy = new RetryPolicy(errorDetectionStrategy,retryStrategy);

            retryPolicy.ExecuteAction(() =>
            ExecuteHTTPGET("https://microsoft.sharepoint.com")
            );

        }

        private static void ExecuteHTTPGET(string requestUri)
        {           
            Console.WriteLine(DateTime.Now);
            throw new FakeTimeoutException("fake timeout");
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri);
            request.KeepAlive = false;
            request.Method = "GET";

            HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse();
            int requestStatus = (int)webResponse.StatusCode;
            webResponse.Close();
        }

5)     Run the program and see the timestamp in the console. Try by switching FakeTimeoutException with SomeOtherException and see the behavior.