In the April release of the Windows Azure SDK Microsoft introduced some new features for the Azure Service Bus. The new feature I want to discus in this post is the Message Pump programming model.
The Message Pump model allows us to get away from the infinite loop you have to use to receive messages from a queue. The following code snippet shows such an approach.
There are two things to note here, one the Receive method has a default timeout which is 60 seconds. This means that within one billing month you would access the queue approximate 45.000 times. If you have a worker role on Windows Azure and you had two instances of it you would now access the queue about 90,000 times. Please keep in mind that these numbers are based on the fact that we have not received any messages yet then those numbers would increase.
Why is this important you say? Well, with your Windows Azure subscription you get 10.000 messages per billing cycle included, after that you pay $0,10 per 10.000 messages. This doesn’t seem a lot of money but it adds up if you process a lot of messages and have multiple instances running.
In my little snippet above I have passed in a TimeSpan which instructs the Receive operation to timeout after 1 hour. If a message comes in before the timeout occurs the receive method will return right away with the BrokeredMessage.
The second thing to keep in mind is that when you run a process in a worker role on Windows Azure, you should never ever leave the while loop. The reason being is that if this was your main processing loop and you would exit from it for any reason you could end up with a Server instance which is now in a recycle mode and this makes it difficult to RDP into if you wanted to diagnose the problem.
The new SDK gives us two new methods called OnMessage and OnMessageAsync accessible from the QueueClient class. The OnMessage method, I’ll focus on this one for now, has two overloads. Both overloads take a callback function as a first parameter while the second overload accepts a second parameter of type OnMessageOptions. The code snippet below shows a sample of the first overload.
The OnMessage method registers the callback which is executed every time a message arrives on the queue. You cannot execute this method a second time or you will get an InvalidOperationException with the message “OnMessage/OnMessageAsync has already been called.”
The use of OnMessageOptions
The OnMessageOptions class allows you to specify some options associated with message pump processing. The two properties, AutoComplete and MaxConcurrentCalls are self explanatory. The ExceptionReceived allows you to register an event handler for when exceptions occur, so you think.
What the ExceptionReceived eventhandler captures
You probably think that the ExceptionReceived event handler captures errors which occur during the processing of the message. You are right of course but it captures a little more than that. As far as I can tell it captures events from three sources:
- Exceptions raised during the time that the message pump is waiting for messages to arrive on the service bus
- Exceptions raised during the time that your code is processing the BrokeredMessage
- And, it is raised when the receive process successfully completes.
This last one is weird to me and I would like to see a separate event handler for it with eventargs specifying which message was successfully completed. Currently, when a message is successfully completed, the sender nor the eventargs object reveal any info about the processed message.
In the code snippet below you find a sample of the LogErrors event handler. I have added the most common exceptions may want to deal with.
Please note, when you have the option “AutoComplete” set to true and an exception is raised, the message will be placed back on the queue. However, if you catch the exception within your message handling process, the queue will not be notified of the exception and as soon as you end your message processing code block, the message will be marked as completed as it was not captured by the ExceptionReceived event handler.
If you want full control of when a message should be marked as completed you should set the AutoComplete to false.
When to expect Exceptions
This is not specific to the use of the Message Pump programming model but I think it is worth mentioning. There are two moments you should be aware of when Exceptions can occur; the first moment is when you are in the process of setting up your QueueClient and register your OnMessage event handler. The Exceptions listed in the LogErrors code snippet can also occur during your setup phase.
The second moment is when the OnMessage event handler has been registered and is waiting for messages to arrive. The good news is that the OnMessage event handler process is very fault tolerant and will recover from deleted queue’s, lost connections and locked queues.
Also added to the V2.0 SDK is the ability to suspend sending and receiving messages to and from queues and topics. In the code snippet below you can see the four possible settings. These status properties allow you to set the queue on hold for receiving messages while you load a batch of message into the queue. When done, you release the queue and all messages are now picked up by the receiving processing instances.
Once you disable a queue, the OnMessage event handler receives a MessagingEntityDisabledException. The OnMessage event handler will continue to monitor when the queue is available all the while throwing those exceptions.
Sample Source Code
I created two small console applications which demonstrate the use of the new Message Pump programming model. The one console app posts 100 messages on your service bus queue all the while locking the queue for receiving.
The second console app monitors the queue and as soon as the queue is available and messages appear in the queue, simple displays the message sequence number and timestamp.
If you play a bit with the MaxConcurrentCalls property on the OnMessageOptions object you will see that you are able to squeeze a few more messages into one second of processing time.
Before you can use the two sample apps, you must first adjust the servicebus connection string in both app.config files.
As always, the Sample Code is not meant to be used as production ready solution but just as learning tool.
Update: As noted by Markus Oberacker it does seem that the messagepump model isn’t much of a help when trying to save costs on making servicebus requests.