While investigating a reported issue in the MQI NodeJS bindings, I had to dig into the C library’s MQI callback functions. I found some behaviour that I didn’t expect. And some slight differences depending on whether you run an application with local or client bindings.
MQI Callbacks
The C MQI can drive callback functions when messages arrive on queues, giving an asynchronous approach for your applications. You don’t need to sit in a blocking MQGET(WAIT) or polling loop to retrieve the messages. The MQCB verb sets up the callback; you give it a function address and the MQ libraries call that function when needed.
When you register the callback function through MQCB, one of the options is to choose which type of callbacks to receive. The MQCBD->CallbackType
field can be set to either MQCBT_EVENT_HANDLER
or MQCBT_MESSAGE_CONSUMER
. If you look at the amqsghac.c sample program, you will see how it uses Events to report on the reconnection attempts made automatically by the client library.
The callback function itself has a number of parameters. For this article, the interesting parameter is the MQCBC (CallbackContext) structure. Within that structure, 3 relevant fields are the Reason
, CallType
and Hobj
.
The unexpected behaviour
I had a program that registered a Message Consumer callback. But I was surprised to find that sometimes the CallType
was set to MQCBCT_EVENT
. Why was I being given an Event, when I had not registered to receive one?
It turns out that this is correct, and is even documented as such. But I wasn’t expecting it. There is a subtle distinction between the CallType
and CallbackType
fields in their respective MQI structures.
With a synchronous MQGET, if you end the queue manager, the verb is given an Reason code like MQRC_QMGR_QUIESCING
. But with the asynchronous operation, the equivalent notification is given by an Event instead of being tied to, say, a MSG_NOT_REMOVED
call type. Basically, failed MQGET responses are split into two types – those that are considered events, and those not.
So a Message Consumer callback function must be prepared to deal with the Event CallType.
Once I’d worked out what was going on, it was fine. It just took a slight reset of how I was thinking of the processing.
Clients and Local bindings
I did find one slight difference between local bindings and client connection. The difference should not be important, and a properly-written program ought not to care. But I thought it worth noting anyway.
When the Message Consumer callback runs in local bindings mode, the associated Hobj
is set to the value that was specified during the corresponding MQCB even when the callback happens for a connection-wide Event such as the queue manager being shutdown.
But in client mode, the Hobj
is set to 0.
Given that the queue manager is being shutdown, you would usually not be expected or able to make any additional “cleanup” calls such as using the Hobj
to MQCLOSE the queue. So the application should always ignore the Hobj
. Depending on how the queue manager is ending, you might have the opportunity to commit/backout any in-flight transactions; those don’t depend on the object handle. But again, this Hobj
difference was slightly confusing.
Conclusion
Seeing an event when I wasn’t expecting one, and seeing different object handle values than I was expecting, made me think at first that there might be a bug in one of the MQI libraries. It turns out that there isn’t.
It mattered to me because of the packages I have released that wrap the C MQI functions for use in other environments. But I thought I’d write this up to help or warn anyone else who might be doing something similar.
since not everyone is reading this blog, may i suggest this be added to the MQCB documentation as a ‘note’ or ‘attention’?
What’s the hObj behaviour for local bindings on z/OS?