MSMQ receive with transaction - rollback not making message available again
Asked Answered
P

2

9

I have this in a class called "MessageQueueReceive".

public MessageQueueTransaction BlockingReceive(out Message message)
{
    MessageQueueTransaction tran = null;
    message = null;

    tran = new MessageQueueTransaction();

    tran.Begin();
    try
    {
        message = Queue.Receive(new TimeSpan(0, 0, 5), tran);
    }
    catch (MessageQueueException ex)
    {
        // If the exception was a timeout, then just continue
        // otherwise re-raise it.
        if (ex.MessageQueueErrorCode != MessageQueueErrorCode.IOTimeout)
            throw ex;
    }

    return tran;

}

Then my processing loop has this:-

while (!Abort)
{
    try
    {
        tran = this.Queue.BlockingReceive(out msg);

        if (msg != null)
        {
            // Process message here

            if (tran != null)
                tran.Commit();
        }
    }
    catch (Exception ex)
    {
        if (tran != null)
            tran.Abort();

    }
}

The control panel tool shows that the message queues I'm using are transactional. Journal queue is not enabled.

This code creates the queue:-

private static MessageQueue CreateMessageQueue(string queueName, bool transactional = false)
{
    MessageQueue messageQueue = MessageQueue.Create(queueName, transactional);
    messageQueue.SetPermissions("Administrators", MessageQueueAccessRights.FullControl,
            AccessControlEntryType.Allow);
    return messageQueue;
}

The transactional parameter is set as "true" when this is called.

What I find is that when an exception occurs during the processing of the message, tran.Abort is called but at that point I'd expect the message to be returned to the queue. However, this is not happening and the messages are lost.

Am I missing something obvious? Can anyone see what I'm doing wrong?

Prinz answered 15/11, 2011 at 22:35 Comment(3)
Is the queue you are receiving on local to the listener service?Raila
Yes, the sending and receiving processes and the queues are all on the same box. They are private queues. I also have the same code running on a few different boxes, and see the same issue on all. That suggests it's related to this code rather than something quirky about the machine.Prinz
Obvious question I guess. Are you sure it is hitting the exception with the abort in? Is it possible that an exception is been thrown in your message handling code that is caught and then thrown away so it never bubbles up to you exception handler with the abort in it?Seriate
P
5

Thanks for all the comments. I did re-organise my code as Russell McClure suggested, and I tried to create simple test cases but could not reproduce the problem.

In the end, the problem was not at all where I was looking (how often does that happen?).

In my pipeline, I had a duplicate message checker. The "messages" my system deals with are from remote devices on a WAN, and occasionally messages on the wire are duplicated.

When a message was pulled from the MSMQ, it would pass via the duplicate checker the database writer. If the database writer failed, the duplicate checked did not remove the hash from its table. When the process tried to loop again, it would get the same message from the queue agan because the MSMQ transaction had been rolled back when the database writer failed. However, on the second attempt, the duplicate checker would spot that it had seen the message before, and swallow it silently.

The fix was to make the duplicate checker spot the exception coming from the next link in the chain, and roll-back anything it had done too.

Prinz answered 26/1, 2012 at 18:9 Comment(0)
K
1

Your queue needs to be created as a transactional queue to get what you want.

enter image description here

EDIT:

Well, if your queue is transactional then that points to the fact that you are mishandling your transaction, although I can't see specifically how it is happening. I would change your BlockingReceive method to return the message. I would move the creation of the MessageQueueTransaction to the outer method. Your code will be much more maintainable if you have the Begin, Commit and Abort method calls in the same method.

Kongo answered 15/11, 2011 at 22:37 Comment(1)
Hi! The queues are definitely created as transactional queues. If I look at the queue properties in Computer Management->Services and Applications -> Message Queuing, it shows as transactional.Prinz

© 2022 - 2024 — McMap. All rights reserved.