9
votes

I'm using Microsoft.Azure.ServiceBus. (doc)

I was getting an exception of:

The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue.

By the help of these questions:

1, 2, 3,

I am able to avoid the Exception by setting the AutoComplete to false and by increment the Azure's queue lock duration to its max (from 30 seconds to 5 minutes).

_queueClient.RegisterMessageHandler(ProcessMessagesAsync, new 
                         MessageHandlerOptions(ExceptionReceivedHandler)
                         {
                             MaxConcurrentCalls = 1,
                             MaxAutoRenewDuration = TimeSpan.FromSeconds(10),
                             AutoComplete = false
                         }
);

private async Task ProcessMessagesAsync(Message message, CancellationToken token)
{
    await ProccesMessage(message);
}

private async Task ProccesMessage(Message message)
{
    //The complete should be closed before long-timed process
    await _queueClient.CompleteAsync(message.SystemProperties.LockToken);
    await DoFoo(message.Body); //some long running process
}

My questions are:

    1. This answer suggested that the exception was raised because the lock was being expired before the long time process, but in my case I was marking the message as complete immediately (before the long run process), so I'm not sure why changing the locking duration from azure made any difference? when I change it back to 30 seconds I can see the exception again.
    1. Not sure if it related to the question but what is the purpose MaxAutoRenewDuration, the official docs is The maximum duration during which locks are automatically renewed.. If in my case I have only one app receiver that en-queue from this queue, so is it not needed because I do not need to lock the message from another app to capture it? and why this value should be greater than the longest message lock duration?
1

1 Answers

14
votes

There are a few things you need to consider.

  1. Lock duration
  2. Total time since a message acquired from the broker

The lock duration is simple - for how long a single competing consumer can lease a message w/o having that message leased to any other competing consumer.

The total time is a bit tricker. Your callback ProcessMessagesAsync registered with to receive the message is not the only thing that is involved. In the code sample, you've provided, you're setting the concurrency to 1. If there's a prefetch configured (queue gets more than one message with every request for a message or several), the lock duration clock on the server starts ticking for all those messages. So if your processing is done slightly under MaxLockDuration but for the same of example, the last prefetched message was waiting to get processed too long, even if it's done within less than lock duration time, it might lose its lock and the exception will be thrown when attempting completion of that message.

This is where MaxAutoRenewDuration comes into the game. What it does is extends the message lease with the broker, "re-locking" it for the competing consumer that is currently handling the message. MaxAutoRenewDuration should be set to the "possibly maximum processing time a lease will be required". In your sample, it's set to TimeSpan.FromSeconds(10) which is extremely low. It needs to be set to be at least longer than the MaxLockDuration and adjusted to the longest period of time ProccesMessage will need to run. Taking prefetching into consideration.

To help to visualize it, think of the client-side having an in-memory queue where the messages can be stored while you perform the serial processing of the messages one by one in your handler. Lease starts the moment a message arrives from the broker to that in-memory queue. If the total time in the in-memory queue plus the processing exceeds the lock duration, the lease is lost. Your options are:

  1. Enable concurrent processing by setting MaxConcurrentCalls > 1
  2. Increase MaxLockDuration
  3. Reduce message prefetch (if you use it)
  4. Configure MaxAutoRenewDuration to renew the lock and overcome the MaxLockDuration constraint

Note about #4 - it's not a guaranteed operation. Therefore there's a chance a call to the broker will fail and message lock will not be extended. I recommend designing your solutions to work within the lock duration limit. Alternatively, persist message information so that your processing doesn't have to be constrained by the messaging.