1

I'm using the MQCB function to add message consumer callback functions for queues that I'm reading from. I'm trying to read from two queues on the same connection, and it seems to work fine when receiving messages: my callback function gets the object handle for the queue that the message was received from.

However, when I get an MQRC_NO_MSG_AVAILABLE event (since I set MQGMO_WAIT on my consumer), the object handle is MQHO_NONE, so I can't tell which queue the event refers to. I could solve that by putting the object handle in the callback context, but is that the way it's supposed to be done? Or am I missing something obvious here?

I'm connecting to a queue manager running version 8.0.0.2 on Linux, using the C client library version 8.0.0.5, likewise on Linux. Here is the output from my sample program, showing object handles being 0:

Opened queue 'AMQ.5A55ED982D616602                            ' with handle 101
Opened queue 'AMQ.5A55ED982D616603                            ' with handle 102
Completion code MQCC_FAILED, reason MQRC_NO_MSG_AVAILABLE, object handle 0
Completion code MQCC_FAILED, reason MQRC_NO_MSG_AVAILABLE, object handle 0

And the program itself:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <cmqc.h>
#include <cmqxc.h>
#include <cmqstrc.h>

void my_message_consumer(MQHCONN, PMQMD, PMQGMO, PMQVOID, PMQCBC);

volatile unsigned events_received = 0;

void
check_fail(const char *action, MQLONG comp_code, MQLONG reason)
{
  if (comp_code != MQCC_OK) {
    fprintf(stderr, "%s failed with %s %s\n",
            action, MQCC_STR(comp_code), MQRC_STR(reason));
    exit(1);
  }
}

int
main()
{
  MQHCONN hconn;
  MQHOBJ hobj1, hobj2;
  MQOD od = {MQOD_DEFAULT};
  char queue_name[MQ_Q_NAME_LENGTH + 1];
  MQLONG c, r;

  MQCONN("", &hconn, &c, &r);
  check_fail("MQCONN", c, r);

  /* Open two dynamic queues */
  strcpy(od.ObjectName, "SYSTEM.DEFAULT.MODEL.QUEUE");
  MQOPEN(hconn, &od, MQOO_INPUT_EXCLUSIVE, &hobj1, &c, &r);
  check_fail("MQOPEN", c, r);

  strncpy(queue_name, od.ObjectName, MQ_Q_NAME_LENGTH);
  queue_name[MQ_Q_NAME_LENGTH] = '\0';
  printf("Opened queue '%48s' with handle %d\n", queue_name, hobj1);

  strcpy(od.ObjectName, "SYSTEM.DEFAULT.MODEL.QUEUE");
  MQOPEN(hconn, &od, MQOO_INPUT_EXCLUSIVE, &hobj2, &c, &r);
  check_fail("MQOPEN", c, r);

  strncpy(queue_name, od.ObjectName, MQ_Q_NAME_LENGTH);
  queue_name[MQ_Q_NAME_LENGTH] = '\0';
  printf("Opened queue '%48s' with handle %d\n", queue_name, hobj2);

  /* Add a callback with zero WaitInterval for both queues */
  MQMD md = {MQMD_DEFAULT};
  MQGMO gmo = {MQGMO_DEFAULT};
  MQCBD cbd = {MQCBD_DEFAULT};
  gmo.Options = MQGMO_NO_SYNCPOINT | MQGMO_WAIT;
  gmo.WaitInterval = 0;
  cbd.CallbackType = MQCBT_MESSAGE_CONSUMER;
  cbd.CallbackFunction = &my_message_consumer;

  MQCB(hconn, MQOP_REGISTER, &cbd, hobj1, &md, &gmo, &c, &r);
  check_fail("MQCB", c, r);
  MQCB(hconn, MQOP_REGISTER, &cbd, hobj2, &md, &gmo, &c, &r);
  check_fail("MQCB", c, r);

  /* Start consuming */
  MQCTLO ctlo = {MQCTLO_DEFAULT};
  MQCTL(hconn, MQOP_START, &ctlo, &c, &r);
  check_fail("MQCTL start", c, r);

  /* Wait until events received */
  while (events_received < 2)
    sleep(1);

  return 0;
}

void
my_message_consumer(MQHCONN hconn, PMQMD md, PMQGMO gmo,
                    PMQVOID buffer, PMQCBC context)
{
  printf("Completion code %s, reason %s, object handle %d\n",
     MQCC_STR(context->CompCode), MQRC_STR(context->Reason),
     context->Hobj);
  events_received++;
}

Compile it with:

gcc -o mq-no-msg mq-no-msg.c -g -Wall -I/opt/mqm/inc -L/opt/mqm/lib64 -lmqic_r -Wl,-rpath=/opt/mqm/lib64

and set the MQSERVER environment variable before running.

legoscia
  • 39,593
  • 22
  • 116
  • 167
  • Yeah, that's what confuses me. I specified [`CallbackType`](https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_8.0.0/com.ibm.mq.ref.dev.doc/q094600_.htm) as "message consumer" instead of "event handler", and that page says "The event handler is called for conditions that affect the whole message consumer environment" (as opposed to a single queue being empty) - so why does my message consumer behave as an event handler in this case?... – legoscia May 09 '18 at 11:28
  • Yeah, I guess I don't really need the "no messages" event - and if I'll need it, it seems like the answer is to accept that this is the way it works, and store what I need in CallbackArea. Thanks! – legoscia May 09 '18 at 12:17
  • Rolled my comments into the answer. – JoshMc May 09 '18 at 13:00
  • I updated my answer with some more details and I added a use case where you may want to receive `MQRC_NO_MSG_AVAILABLE` event messages in your consumer, but as you found by default this would be less than useful if you have more than one consumer registered, use the `CallbackArea` could be a way around this issue. – JoshMc May 09 '18 at 23:30
  • If you have access to open a PMR it may be worth clarifying with IBM. The KC states regarding the `Hobj` value, `For an event handler, this value is MQHO_NONE`, and as you correctly point out you are not registering a event handler. I also did not find any direct reference in the KC that event messages would be sent to a consumer, but the IBM presentations I referenced state events that are sent to a consumer are only those events that are pertinent to the queue it is consuming from. It would seem reasonable to me that the `MQCBC` should include the associated `Hobj` not `MQHO_NONE`. – JoshMc May 09 '18 at 23:36

2 Answers2

1

I found information from a few sources on this topic that when put together paint the whole picture (unfortunately IBM's MQ KC does not document this very well to say the least).

  1. At Capitalware's MQ Technical Conference v2.0.1.3 Morag Hughson gave a presentation WebSphere MQ V7 Enhanced Application Programming which has some useful information.

    On page six it states:

    • Your message consumer can also be called with CallType set to MQCBCT_EVENT_CALL (this is also the only way an Event handler can be called). The message consumer will be given events that are pertinent to the queue it is consuming from, for example, MQRC_GET_INHIBITED whereas the event handler gets connection wide events.
  2. In the IBM MQ v8 KC page MQCBC - Callback context > Fields for MQCBC > Hobj (MQHOBJ) it states:

    For an event handler, this value is MQHO_NONE

  3. The IBM provided sample amqscbf0.c also demonstrates checking the pContext->CallType in the MessageConsumer, if it is type MQCBCT_EVENT_CALL it prints the Reason, if it is type MQCBCT_MSG_REMOVED it prints the message.


Based on the above information it appears the behavior you see is the expected behavior.


A suggested work around would be to set the CallbackArea field of each queue's MQCBD with a unique value that you can use to determine the which queue the event refers to.


On page four of Morag's presentation "WebSphere MQ V7 Enhanced Application Programming" it states the following:

  • MQGMO_WAIT with MQGMO.WaitInterval = 0 operates just like MQGMO_NO_WAIT when one uses on an MQGET, but in the case of asynchronous consumers we wish to avoid the consumer from polling in a busy loop in this case, so it operates more like a backstop marker to show when the end of a batch of messages has been reached.

In the table on the same page it under the Asynchronous Consume column for MQGMO_WAIT with MQGMO.WaitInterval = 0 it states:

Only called with MQRC_NO_MSGS_AVAILABLE if just started or has had a message since last 2033

Your consumer will not continuously get events notifying it that no messages are on the queue. An event is only generated if there are no messages when the call back is first started and/or after each time ALL messages have been read (GET) from the queue. Essentially it lets you know that currently no more messages are available after it had read at least one message. This may be useful if you expect batches of messages and want to perform some action after all messages in a batch have been read off the queue.

  • Note that MQGMO_NO_WAIT, and MQGMO_WAIT with a WaitInterval of MQWI_UNLIMITED are quite different when passed to MQGET but with the MQCB call their behaviour is the same. The consumer will only be passed messages and events, it will never be passed the reason code indicating no messages. Effectively MQGMO_NO_WAIT will be treated as an indefinite wait. This is to prevent the consumer from endlessly being called with the no messages reason code.

If you really do not need the MQRC_NO_MSG_AVAILABLE event messages, then MQGMO_NO_WAIT is probably the way to go.

JoshMc
  • 10,239
  • 2
  • 19
  • 38
0

When you got the MQRC_NO_MSG_AVAILABLE as an event, it meant there was no message (matching your criteria, if you specified any criteria) on any of the queues you registered. So there is no need to supply the callback with any particular HObj in this case.

  • So if you get the `MQRC_NO_MSG_AVAILABLE` return code or something else like `MQRC_GET_INHIBITED` how would the application know which queue it was related to so that it could take an appropriate action if required? The OP has a valid point that this does not seem to match the expected behavior. – JoshMc May 11 '18 at 23:45
  • The no-msg-available relates to all queues you registered, not only one. About get-inhibited, this relates to one queue. –  May 12 '18 at 10:22
  • Can you confirm then that a `MQCBCT_EVENT_CALL` message sent to the consumer with `MQRC_GET_INHIBITED` has `Hobj` set to a value but when a `MQCBCT_EVENT_CALL` message sent to the consumer with `MQRC_NO_MSG_AVAILABLE` that `Hobj` is set to to `MQHO_NONE`? Is this behavior you have observed? Based on the IBM presentations the `MQCBCT_EVENT_CALL` messages that are not pertinent to a specific queue are sent to a registered event handler, in this case the OP does not have a event handler but each registered consumer when the queue was empty received the `MQRC_NO_MSG_AVAILABLE`. – JoshMc May 12 '18 at 11:20
  • Based on the IBM presentation, if the `MQRC_NO_MSG_AVAILABLE` was not specific to each queue it should not have been sent to the registered consumer. I also believe but have not tested that if the consumer receives a `MQRC_NO_MSG_AVAILABLE` it will also have `Hobj` set to to `MQHO_NONE`. – JoshMc May 12 '18 at 11:22
  • Alter amqscbf0.c to dump out the pContext->Hobj at the top of the MessageConsumer function and you'll see the hobj that is inhibited (rc=2016) and reenabled (rc=2494) whenever you type ALTER QL() GET(DISABLED) or GET(ENABLED). –  May 12 '18 at 14:51