14
votes

Can someone offer some more guidance on the use of the Azure Service Bus OnMessageOptions.AutoRenewTimeout http://msdn.microsoft.com/en-us/library/microsoft.servicebus.messaging.onmessageoptions.autorenewtimeout.aspx

as I haven't found much documentation on this option, and would like to know if this is the correct way to renew a message lock

My use case:

1) Message Processing Queue has a Lock Duration of 5 minutes (The maximum allowed)

2) Message Processor using the OnMessageAsync message pump to read from the queue (with a ReceiveMode.PeekLock) The long running processing may take up to 10 minutes to process the message before manually calling msg.CompleteAsync

3) I want the message processor to automatically renew it's lock up until the time it's expected to Complete processing (~10minutes). If after that period it hasn't been completed, the lock should be automatically released.

Thanks

-- UPDATE

I never did end up getting any more guidance on AutoRenewTimeout. I ended up using a custom MessageLock class that auto renews the Message Lock based on a timer.

See the gist - https://gist.github.com/Soopster/dd0fbd754a65fc5edfa9

3

3 Answers

14
votes

To handle long message processing you should set AutoRenewTimeout == 10 min (in your case). That means that lock will be renewed during these 10 minutes each time when LockDuration is expired.

So if for example your LockDuration is 3 minutes and AutoRenewTimeout is 10 minutes then every 3 minute lock will be automatically renewed (after 3 min, 6 min and 9 min) and lock will be automatically released after 12 minutes since message was consumed.

1
votes

To my personal preference, OnMessageOptions.AutoRenewTimeout is a bit too rough of a lease renewal option. If one sets it to 10 minutes and for whatever reason the Message is .Complete() only after 10 minutes and 5 seconds, the Message will show up again in the Message Queue, will be consumed by the next stand-by Worker and the entire processing will execute again. That is wasteful and also keeps the Workers from executing other unprocessed Requests.

To work around this:

  1. Change your Worker process to verify if the item it just received from the Message Queue had not been already processed. Look for Success/Failure result that is stored somewhere. If already process, call BrokeredMessage.Complete() and move on to wait for the next item to pop up.

  2. Call periodically BrokeredMessage.RenewLock() - BEFORE the lock expires, like every 10 seconds - and set OnMessageOptions.AutoRenewTimeout to TimeSpan.Zero. Thus if the Worker that processes an item crashes, the Message will return into the MessageQueue sooner and will be picked up by the next stand-by Worker.

1
votes

I have the very same problem with my workers. Even the message was successfully processing, due to long processing time, service bus removes the lock applied to it and the message become available for receiving again. Other available worker takes this message and start processing it again. Please, correct me if I'm wrong, but in your case, OnMessageAsync will be called many times with the same message and you will ended up with several tasks simultaneously processing it. At the end of the process MessageLockLost exception will be thrown because the message doesn't have a lock applied. I solved this with the following code.

_requestQueueClient.OnMessage(
            requestMessage =>
            {
                RenewMessageLock(requestMessage);
                var messageLockTimer = new System.Timers.Timer(TimeSpan.FromSeconds(290));
                messageLockTimer.Elapsed += (source, e) =>
                {
                    RenewMessageLock(requestMessage);
                };
                messageLockTimer.AutoReset = false; // by deffault is true
                messageLockTimer.Start();

                /* ----- handle requestMessage ----- */

                requestMessage.Complete();

                messageLockTimer.Stop();
            }

 private void RenewMessageLock(BrokeredMessage requestMessage)
    {
        try
        {
            requestMessage.RenewLock();
        }
        catch (Exception exception)
        {

        }
    }

There are a few mounts since your post and maybe you have solved this, so could you share your solution.