views:

2549

answers:

4

I'm writing a .NET Windows Forms application that will post a message to a Websphere MQ queue and then poll a different queue for a response. If a response is returned, the application will partially process the response in real time. But the response needs to stay in the queue so that a daily batch job, which also reads from the response queue, can do the rest of the processing.

I've gotten as far as reading the message. What I haven't been able to figure out is how to read it without removing it.

Here's what I've got so far. I'm an MQ newbie, so any suggestions will be appreciated. And feel free to respond in C#.

Public Function GetMessage(ByVal msgID As String) As MQMessage
    Dim q = ConnectToResponseQueue()
    Dim msg As New MQMessage()
    Dim getOpts As New MQGetMessageOptions()
    Dim runThru = Now.AddMilliseconds(CInt(ConfigurationManager.AppSettings("responseTimeoutMS")))
    System.Threading.Thread.Sleep(1000) 'Wait for one second before checking for the first response'
    While True
        Try
            q.Get(msg, getOpts)
            Return msg
        Catch ex As MQException When ex.Reason = MQC.MQRC_NO_MSG_AVAILABLE
            If Now > runThru Then Throw ex
            System.Threading.Thread.Sleep(3000)
        Finally
            q.Close()
        End Try
    End While
    Return Nothing 'Should never reach here'
End Function

NOTE: I haven't verified that my code actually removes the message. But that's how I understand MQ to work, and that appears to be what's happening. Please correct me if that's not the default behavior.

+5  A: 

You need to open the queue with MQOO_BROWSE option. Then on your first read you do a GET using the MQGMO_BROWSE_FIRST option. Finally, your subsequent GET's should use the MQGMO_BROWSE_NEXT option.

Note: MQOO is MQ open options and MQGMO is MQ Get Message Options.

mamboking
That got it. Thanks a lot.
John M Gant
+1  A: 

You really should be doing this with separate queues. The End of Day processing should have its own queue. After you process your part of the message, you send it on to the EOD queue.

With the Browse option you are going to have to keep track of what messages you've already processed somewhere.

Also, you can set a wait timeout on the GET. So you don't need to "wait 1 sec before checking queue". As written right now you can't hit the no msg available condition because you didn't set NOWAIT in the get message options.

jmucchiello
+1 Good point. I didn't mention in my post that I'm doing a single request and response at a time. The correlation ID of the response will match the message ID of the request, so keeping track of the messages I've already processed shouldn't be a problem. As far as the wait timeout, I had seen that but hadn't figured out what to do with it. I've done some more research based on your suggestion and cleaned up the method considerably using that approach. Thanks a lot!
John M Gant
A: 

For posterity's sake, here's a (I think) much improved version of the method based on mamboking and jmucchiello's answers.

Public Function GetMessage(ByVal correlID As Byte()) As MQMessage
    Dim waitInterval = CInt(ConfigurationManager.AppSettings("responseTimeoutMS"))
    Dim q As MQQueue = Nothing
    Try
        Dim msg As New MQMessage()
        Dim getOpts As New MQGetMessageOptions()
        q = ConnectToResponseQueue()
        msg.MessageId = MQC.MQMI_NONE
        msg.CorrelationId = correlID
        getOpts.MatchOptions = MQC.MQMO_MATCH_CORREL_ID
        getOpts.WaitInterval = waitInterval
        getOpts.Options = MQC.MQGMO_BROWSE_FIRST Or MQC.MQGMO_WAIT
        q.Get(msg, getOpts)
        Return msg
    Finally
        If q IsNot Nothing AndAlso q.IsOpen() Then q.Close()
    End Try
End Function
John M Gant
A: 

I realize I'm coming to this discussion a bit late and you've probably already coded this app up. In the event you ever need to modify it or for anyone else who might need to do something similar, I have a couple of observations.

First, if you can do this with a v7 QMgr and a v7 WMQ client this would be the preferred solution. In v7, the .Net support has been moved from a SupportPac to part of the base product. There is considerable new functionality, some bug fixes and better performance. Also, on v7 you can use pub-sub...which brings me to my second observation.

Based on the description in the original post, I would have done this in Pub-Sub. The app which puts the message needs only put one and it does not even need to know it's putting to a topic. You can actually put up an alias over a topic that makes it look like a queue to the message producer. Your consuming apps can then either subscribe or you can make two administrative subscriptions so that the published messages go to two queues that you designate. Your apps then each have a dedicated queue and no coding changes are involved for the producer and the batch app, it's just configuration. Of course the app driving the transactions would need to actually consume the messages rather than browse them.

The advantages here are several:

  • As the queue fills up with messages, indexing gets flushed to disk and above a threshold you'll see a performance hit which may be significant. Therefore, the current method doesn't scale all that well.
  • With the pub-sub method you can have multiple instances of either the real-time or batch apps or both, and these can be on the same or different QMgr. Scaling up is easy.
  • You eliminate the dependency between the real-time and batch apps needing to be on the same QMgr.
  • More transparent administration. If you see messages building up in the real-time queue you know you have a problem.

A couple of completely different issues here as well. One of these is to use the Fail if Quiescing option. The purpose of this is that when the QMgr is shut down cleanly this option causes your API call to end with a return code indicating the QMgr is shutting down. If you do not include this option then it is possible with two or more connected apps that the QMgr will never shut down cleanly and need to be forced down or have its processes killed with brute force. As a rule, always use Fail if Quiescing on all API calls that support it. The reason it exists at all is for people who need XA transactionality but for some reason can't use it. In this scenario, the CONNECT and the first GET or PUT call uses Fail if Quiescing set and subsequent GET or PUT operations do not. This causes the QMgr to wait for the entire set of GET/PUT calls to complete but then the next CONNECT or GET/PUT uses Fail if Quiescing so the QMgr has a chance to shut down if necessary.

The other observation here is that there is no Catch in the code here. I'm guessing there's one in scope further up the call stack? It is always advisable to print the WMQ return code from an exception so that you can track down the root cause. On consulting engagements I always advise clients that failure to print the return code (or the linked exception for JMS/XMS code) is a showstopper that should prevent promotion of an app to Production. It's really that important. Even if you have a catch in the code that calls getMessage(), someone reusing the example code snippet here might not realize this important piece is missing.

T.Rob