Azure Service Bus - AutoRenewTimeout
I was reading a
question
on StackOverflow where the requirement was to "Lock a
Service-Bus Queue and prevent others from accessing it."
Quite often when dealing with competing consumers and
PeekLock mechanism it feels odd. What do you
mean I'm in the middle of processing my message and it will
re-appear on the queue?! Why do I need to worry about some
LockDuration?! The answer is simple. The server
allows the receiver to handle the message and completes it
within LockDuration time. If the operation
takes longer, the server will no longer respect the original
lock token as it will be replaced by another receiver that
got the message.
This is great when message processing is under 1 min. But
what if message processing takes longer. In case message
processing does take longer, the message will re-appear on
the queue. This can be controlled by increasing
LockDuration. Even then, the maximum amount of
time is only 5 minutes. What will happen if message
processing exceeds 5 minutes? You've guessed it right; the
message will be unlocked and handled by another receiver.
There are two options to address this:
- Manual lock renewal
- Automatic lock renewal
Manual Lock Renewal
BrokeredMessage allows renewing an already
obtained lock that was given when message was received.
var brokeredMessage = queueClient.Receive();
brokeredMessage.RenewLock();
It surely looks simple. What's not so trivial is the timing of when lock renewal should be issued. Not mention that lock renewal time management would pollute the code with an additional concern. Let's look at a better option.
Automatic lock renewal
In one of the previous posts, I have covered the
OnMessage API. One
of the OnMessageOptions was
AutoRenewTimeout. What this does is
automatically renew message lock without increasing the
delivery count up maximum to the time of
AutoRenewTimeout. For example, setting it to 10
minutes will allow us to surpass the maximum 5 minutes of
the LockDuration and allow message processing
to take up to 10 minutes. Auto lock renewal will not be
issued if callback takes longer than 10 minutes.
Let's dive into a sample:
static async Task Go(NamespaceManager nsManager, MessagingFactory mf)
{
var connectionString = Environment.GetEnvironmentVariable("ConnectionString");
var nsManager = NamespaceManager.CreateFromConnectionString(connectionString);
var mf = MessagingFactory.CreateFromConnectionString(connectionString);
if (!await nsManager.QueueExistsAsync("test").ConfigureAwait(false))
{
var desc = new QueueDescription("test")
{
LockDuration = TimeSpan.FromSeconds(45),
};
await nsManager.CreateQueueAsync(desc).ConfigureAwait(false);
}
var msg1 = new BrokeredMessage(new string('A', 5));
msg1.MessageId = DateTime.Now.ToString();
var sender = await mf.CreateMessageSenderAsync("test").ConfigureAwait(false);
await sender.SendAsync(msg1).ConfigureAwait(false);
var receiver = await mf.CreateMessageReceiverAsync("test");
var options = new OnMessageOptions
{
AutoComplete = false, // let us complete the message
AutoRenewTimeout = TimeSpan.FromMinutes(10)
};
//callback
receiver.OnMessageAsync(async (message) =>
{
var sw = new Stopwatch();
Console.WriteLine("Callback started for message id " + message.MessageId);
Console.WriteLine("delaying for 8 minutes");
await Task.Delay(TimeSpan.FromMinutes(8));
var body = message.GetBody<string>();
Console.WriteLine($"processing id: {message.MessageId} body: {body}");
Console.WriteLine("delivery count: " + message.DeliveryCount);
await message.CompleteAsync();
Console.WriteLine("Callback stopped");
}, options);
Util.ReadLine();
}
For this test code, a queue called test with
LockDuration 45 seconds is used. The message
received in the callback is handled for over 5 minutes and
completed after 8 minutes.
Fantastic, we can obtain message lock for longer than 5
minutes! Just don't go crazy. You should strive to have
shorter processing and not lock message for a long time. If
you do, review what you're trying to do. Remember that
processing that is stalled will be holding up the message
until AutoRenewTimeout time is expired. Which
at times is not a great idea.
Happy long processing!
Update 2017-06-16
Since this post there were still questions about guarantees
on this operation. Due to the fact that this is ASB client
initiated renewal, it's no different from
brokeredMessage.RenewLock();. If operation
fails after all the retries ASB client has in place, the
lock won't be re-acquired and message will become visible.
I.e. AutoRenewTimeout is not a
guaranteed on the broker operation.