MQ API Exits – FAQ

Exit

In the past few months I seem to have been been sent a bunch of questions about MQ API Exits from a variety of developers. Some of the questions were repeated so I thought I’d try to collect them, tidy them up, and turn them into an FAQ article.

API Exits were originally made available for one platform in MQSeries V5.2 and then extended to the rest of the Distributed family in V5.3 so they have been around for a while.

Exits are an advanced topic and I’m not planning on going into much general information about these exits. I will assume that someone interested in this article already knows the basic principles and is comfortable working in this environment. For more information on the interfaces see the product documentation and the sample amqsaxe0.c exit.

Contents

General

Why might I use API Exits?

The main reasons for using an API Exit are firstly to monitor what an
application is doing:

  • Auditing
  • Problem determination
  • Performance monitoring
  • Reviewing application conformance eg correct use of flags in API calls

Or for modifying application requests:

  • Enforcing standards such as always using FAIL_IF_QUIESCING
  • Adding metadata to messages
  • Security eg encryption

What platforms can I use API Exits on?

API Exits (and other exits I might mention in this FAQ) are features on the Distributed platforms. Although there is an API Crossing Exit for MQ on z/OS, it is very different and only available in the CICS environment. So we will ignore that.

How do API Exits compare to other approaches to managing what applications do?

The main alternative to API Exits is the Application Activity Trace. They both give the ability to see what applications are doing, but in very different ways.

  • Application Activity Trace is asynchronous, processed some time after the application has run
  • Application Activity Trace does not require any additional installation, or specialist code. Sample programs are provided with MQ that can format the generated event messages.
  • Application Activity Trace is “read-only”: it cannot affect what the applications are doing
  • API Exits are synchronous, called in-line with the application code
  • API Exits can modify the MQI parameters as they pass between the application and the queue manager

What languages can I use to write API Exits

Only C is a supported language for writing these exits.

Environment

My exit is only needed for specific applications. How do I stop my exit being called for all apps?

The exit is loaded for all applications. During the initialisation phase, you can look at the pExitContext->Environment and the pExitParms->APICallerType to get a lot of information about the type of caller.

At the highest level, the APICallerType is split between INTERNAL (which is only set by some of the MQ product processes) and EXTERNAL (all user applications and some other product processes). The Environment field is another filter you can apply to your decision.

For example, one of my exits that wants to intercept all user-invoked calls has

  if (pExitContext->Environment == MQXE_MCA_SVRCONN)
    interceptCalls()
  else if (pExitContext->Environment == MQXE_OTHER && pExitParms->APICallerType == MQXACT_EXTERNAL)
    interceptCalls()
  else
    /* not interested in these qmgr connections */

That gets all direct connections and all those coming from clients trapped including clients written in languages such as Java and .Net which cannot support API exits running inside their own processes. The interceptCalls function then calls MQXEP to register the MQI verbs I want to work with. See the section on Message Properties for a way an exit can determine if it is running inside the stack of a C-based MQ Client application.

Remember that a few queue manager processes have special MQXE_ values. I used that in one exit where I was only interested in trapping operations done by the command server.

A further filter that you may want to use is based on the application name. Before MQ 9.1.2, that was available during the exit’s initialisation function. However now that applications can set their own names as one of the MQCONNX parameters, the name passed to the exit may start off as blank and only be available later in the processing. So you may wish to always register the MQI verbs to be intercepted during connection initialisation, and then unregister them later when you know that the application name is not one you want.

What is the link between thread and connection handle? Might one thread process other connections? Or might one connection handle be processed by more than one thread?

There is no relationship between thread and hConn.

Each thread may be handling multiple hConns; each operation on an hConn could be issued by different threads. The exit is running directly under the application’s stack so follows whatever behaviour the application has coded.

Can two application MQI calls be active simultaneously on the same thread? What is the serialisation?

Once an MQI call has been started, it remains on the same thread for the rest of its execution. That includes the calls to BEFORE and AFTER exit points. So if your exit is called for, say, PutBefore you know that that the next thing seen in that thread of your exit will be the PutAfter for the same queue. That means you can use techniques such as stashing some information in thread-local-storage during the BEFORE, and using it in the AFTER. A common reason for doing that is to restore parameters such as pointers to buffers that your exit might have replaced for the MQPUT, but which need to be given back to the application unchanged. The next operation for that queue’s object handle may be on a different thread of course.

You can also use the MQAXP->ExitUserArea as a space to hold temporary stashed pointers as that area is maintained across all calls to the exit for a given hConn – different hConns will have a different space. Usually that space holds a single pointer to a larger area allocated during your exit’s initialisation (perhaps a structure containing control data you need to maintain), and it can then be deallocated during the termination phase.

A variation on this happens in the MQ CallBack/MQCB operation for asynchronous delivery of messages. The exit is called by the queue manager on the same thread with which the application will get the message data. The stack can be thought of as inverted here – the thread is “locked” until the return from the exits and the application code. While it’s possible to call MQI verbs from within the callback processing, this must be done on the same thread. I’ve expanded on CallBack in a later Q&A.

What version of a structure does an exit see?

The exit sees the structures such as the MQOD or MQMD exactly as the application has used them. So you may not see the most recent version of a structure. If you want to make use of fields that were added to later versions of the structure, then you should define a local copy of the structure in your exit, copy the application’s values into your copy, make sure you have the newer version set in the structure, and then reset the parameter that is passed down into the queue manager.

That is why a lot of parameters to the exit are defined as pointers-to-pointers – it allows replacement of parameters. Then on return from the queue manager (the AFTER phase) you have to copy anything returned back to the application and restore the original structure pointer so that the application can work with what it needs to.

You cannot rely on the application having allocated sufficient space for the newer version of the structure.

A possible problem

As an example of a potentially dangerous error, I was recently looking at an old open-source API exit. It intercepts MQPUT requests, and in the PutAfter function it had the line

memcpy(&md, *ppMsgDesc, sizeof(MQMD));

The problem here is that the application may have explicitly allocated and be using an MQMDv1 structure and (assuming it was compiled after MQ V5.0) the line in this exit is copying sizeof(MQMDv2) bytes as that is the default structure size even though the initialisation sets MQMD.Version to 1. So you might be ending up with random bytes in the local copy of the MQMD. Instead the exit should be doing something like:

if ((*ppMsgDesc)->Version == MQMD_VERSION_1)
memcpy(&md, *ppMsgDesc, MQMD_LENGTH_1);
else
memcpy(&md, *ppMsgDesc, MQMD_LENGTH_2);

Similarly for copying structures back to application space – make sure you only copy bytes appropriate for the original version structure. Rather than random bytes, you might actually overwrite and corrupt other pieces of the application data.

Another problem – what comes out may not go in

One thing to remember is that parameters to AFTER functions may have been modified by the queue manager from those originally passed by the application. That is normally a good thing – it means that you can look at the MsgId and CorrelId fields that have been assigned as part of an MQPUT. But one situation took me a little while to work through. The application (actually a receiver MCA in this case) was putting an MQMDv1 structure, with an MQMDE as part of the message body. In itself, that’s perfectly fine. The MQMDE was created to allow older applications to continue to work compatibly. But an AFTER exit was then using the application pointers to do a second MQPUT. Although there was no error return code to the MQPUT, tracing showed an error (essentially a badly-formed message) being dealt with by the queue manager as best it could. And that in turn caused the duplicate message to appear corrupt.

The problem was that during the real PUT activity, the MQMD was getting modified by the queue manager to merge the MQMDE into an internal representation of an MQMDv2 structure, and modifying the CCSID/Encoding fields so they referred to the real user data that came after the MQMDE. Those modifications made a repeat operation fail as the visible v1 portion of the MQMD was still pointing at the MQMDE, and the modified CCSID/Encoding broke the rules for chaining as they no longer referred to the contents of the MQMDE. Because this was happening under an channel process, other parts of the MQMD like the MsgId and the context fields were already set, but I’ll just reiterate that under application code that initially creates a message, they may of course change between the BEFORE and AFTER.

My preferred solution for this is to stash the CCSID/Encoding during a BEFORE operation and reset them temporarily in the corresponding AFTER exit’s view of the MQMD. This may be something that happens only in the case of an MQMDv1+MQMDE as I couldn’t think of another similar scenario, but it would still not surprise me to find something else that hits a related issue.

Can an exit call the MQI itself?

Yes. But it should not use the verb directly by its usual name. Instead the exit should call via indirect function pointers passed to it. This is recommended because of how MQ libraries may get dynamically loaded underneath the application as part of the support for multiple product installations.

So, do not call MQOPEN. Instead use pExitParms->Hconfig->MQOPEN_Call. The parameters are the same as the regular MQI verbs. API Exits are not recursive – calls from one into the MQI are not intercepted by any exits including itself.

Can I configure multiple exits? What order are they called in?

You can configure multiple exits with stanzas in the qm.ini and mqs.ini files. One of the attributes in the stanza is called Sequence. This is a number that controls which order exits are invoked.  During Before operations, the exits are invoked in ascending order; during After operations, the order is reversed. That means that do/undo processing can take place in a predictable sequence.

For example, consider two exits where one does encryption/decryption and one does compress/decompress. Clearly you want the compression to happen before encryption. During an MQPUT, the PutBefore works through the exits in numeric order so we want the compression exit to have a lower Sequence than the crypto package. During  an MQGET, to restore the original message content, the decompress must happen after the decryption. Because the work is done in a GetAfter call, the reversed numeric sequence is applied and everything works as it should.

Will two exits interfere with each other?

The exits will not really interfere with each other but one will see the effects of an exit called before it. So with the previous example, the encryption exit will see the message body that has already been modified by the compression exit, and can encrypt the smaller amount of data.

Of course, the two exits might conflict in specific ways – for example if both wanted to set the same named property in a message. But a properly-designed exit should ensure that kind of thing is either configurable or given a unique scoping name.

Can I tell which version of queue manager the application is connected to?

You can get the queue manager CMDLEVEL and VERSION via MQINQ. The VERSION is a string corresponding to the VRMF. Do an MQOPEN/MQINQ/MQCLOSE for the qmgr object from within the exit. That could be issued in the ConnxAfter phase. It’s possible that the MQINQ might fail in some apps because of authorisations, but should not in the case of channel programs.

Using MQINQ to get information about the queue manager is very standard. Even the MQ JMS implementation uses it internally. If the call fails, then there should be suitable fallbacks and defaults taken, but it is very common to use this check.

Why is my exit seeing calls that have not been made by the application?

I’ve already alluded to the fact that the application interface layer may issue additional API calls. The most common is the MQINQ done by the JMS code to extract some information about the queue manager. The JMS layer maps that API to the MQI so that a JMS Producer might end up calling MQPUT or MQPUT1. The API Exit sees the real MQI calls.

But some other operations might be seen when your exit is executing in the SVRCONN space on behalf of client applications, and which might not be precisely what the client has coded. An MQGET with WAIT might get transformed by the SVRCONN code into an MQCB + Callback for efficiency. This is transparent to the application, but it may be visible in an exit.

Another thing you might see is when the client opens SYSTEM.PROTECTION.POLICY.QUEUE. That queue holds configuration information for the Advanced Message Security feature; the client layer reads that queue to decide whether message data needs to be encrypted. Because it’s using the same connection as the application MQI calls, you will see the access in a SVRCONN exit.

Why do I see a successful call, even when a client program fails to connect?

If your exit is running inside the SVRCONN process (usually amqrmppa) then what an exit sees are the MQI calls made by that process. Not the MQI calls made by the client program. I mentioned above that you might see additional or slightly different MQI calls. But it shows up even more during the connection process. What you see is the success or failure of the channel program’s connection.

For example, you might think that the ConnAfter call might show the channel name in the MQAXP structure. But the process does not actually know at that point what the channel name is. What you see is the MCA_SVRCONN program doing its connect to the qmgr on initial receipt of the startup request. There are several other non-MQI interactions that go on, including transmission and validation of the channel name, and setting the user’s security context, after the MQCONN but before the return code goes back to the application. The API Exit is reflecting the MQI calls made by the directly-calling process, not the remote program. 

And that’s why the interceptor in the SVRCONN process will usually show a successful MQCONN even if the client program ends up with a failure like unknown channel name. The failure is reflected back to the client outside of any MQI call made by the SVRCONN to the queue manager.

What operating system privileges does an exit have? What is the process context?

An API Exit runs inside the application stack. In the case of an app connecting to a local queue manager, that means it runs with the OS context of the user running the program. For a client-connected application, then it is possible to run API Exits inside the process if the C client libraries are in use, but more commonly you would expect to be running inside the SVRCONN channel process environment, with the same level of OS privilege as the queue manager itself.

Running inside the application process means that the exit is very close to the parameters as provided by the application (before flattening and normalisation of structures might occur). And it also means that the exit has access to user-private credentials such as certificates that may be needed to manipulate messages.

It also means that the exit is constrained in its use of MQ objects to the same objects the user of the program has – the exit cannot open a queue unless the user already has that authority.

How should I provide configuration for my exit? Where should output be sent?

Logs and configuration files probably should be in a directory associated with the queue manager name to allow separation. It’s best to avoid anything going into the /var/mqm tree, again to keep it separated from product data. Remember that there are some limits to fields in the API Exit stanza in the qm.ini file so you can’t expect to put a full path name in there eg to name an output directory unless it’s somewhere short like /var/log/apix. Although the amqsaxe0 example exit uses environment variables to name some of these attributes, I don’t like that as a more general approach as the variables would have to be set globally for all applications.

One thing to remember is that, in general, because an API exit is running under application context, output directories may need to be world-writable and configurations may need to be world-readable. Perhaps output gets sent via a proxy process which has separate user configuration. This becomes less of a concern when all user applications are client-connected; then the exit is only ever run inside queue manager processes, and has its level of operating system access.

Can I tell if my exit has actually been loaded?

If the queue manager cannot load the specified exit, it will not start and you will get an error message during strmqm..

However, an API Exit will normally be provided in four formats – 32 and 64-bit each in both threaded and unthreaded modes. The queue manager startup requires only the 64-bit threaded (_r) version. So if any of the other three formats are missing or wrong, then it can’t be validated during queue manager startup.

You will instead see problems with those other formats as an MQRC_API_EXIT_LOAD_ERROR (2183) when an application of the relevant type tries to connect. There is also an entry in the queue manager error log.

There is no positive notification of successful loading beyond anything that the exit itself might do. Absence of any failure indications means that the exit has been picked up corectly. You do, of course, see both successful and failed loading of exits in any trace output.

Are there any constraints about using threads in an exit?

There should be no problem spawning your own thread for I/O inside a threaded exit. At one point, some platforms did not allow threaded code to be called inside “non-threaded” applications – the runtime startup modules like crt0.o were different and the non-threaded version did not initialise things needed for threads – but I’ve not seen that distinction in recent years. I’ve seen exits that are supposedly running in non-threaded environments (ie the application is linked with libmqm.so) but which still successfully use threaded APIs. We do apply some controls though – you can’t setup an MQ callback function via MQCB from a nominally non-threaded application.

MQ will allow different threads to be used for an hConn provided a suitable MQCNO_HANDLE_SHARE_* option is set during connection. With SHARE_NONE, which is the default, applications can only ever use the hConn, and hence dependent hObj handles, on the same thread on which it was created. Unless your exit changes the app’s option during connection, the exit will inherit the requested behaviour. Only one MQ verb can be really active for the application-level hConn at a time, even with shared handles. SHARE_BLOCK will serialise them transparently with a lock while SHARE_NO_BLOCK would return an immediate error. Most applications are probably not aware of those handle options; some wrapper layers may enforce particular values.

I’ve never tried to use the active hConn on a different thread while inside an API Exit. Although an exit is allowed to make a nested call to the MQI, there might be a test for the thread id still being the same while the application’s verb is in progress. It’s not something I’d recommend, but you would need to test if it really works or not.

Running your own exit’s thread completely asynchronously and effectively calling MQ verbs at random points compared to the application’s operations, you’d be subject to the MQCNO_HANDLE rules if you use the application’s hConn. And if you created your own connection in that side thread, you’d obviously lose any transactional integrity of the application’s PUT and your PUT being in the same UOW.

Exit intialisation and termination

The API Exit interface defines two operations that are not part of the MQI: initialisation and termination. These come associated with the MQXR_CONNECTION reason, and do not have separate Before or After phases. The lifecycle for these verbs is still connection-based. The MQXF_INIT is called during the MQCONN, and MQXF_TERM is called during MQDISC. There is no process-wide interface for API Exits.

This means that if you want to allocate something for the lifetime of the process (for example opening a logfile) then you have to manage that global state yourself. Perhaps maintaining an initCountvariable that is incremented for each MQXF_INITand decremented during MQXF_TERM. Only close the log file when the initCount has gone back to zero. For some global resources, you might be happy to let them hang around until they are automatically cleaned up by the OS when the application program ends. But that’s your design choice.

Puts & Gets; Queues & Topics

When an MQPUT happens, how do I know the name of the queue?

Just as in normal application code, the only direct knowledge of the queue is via the object handle. Mapping that to the queue name requires you to trap MQOPEN calls and store the queue name along with the returned object handle. You can then do the lookup during MQPUT/MQGET.

Your exit will, of course, see the name of the queue as specified by the application code. That could be an alias or remote queue, or one to be resolved by a cluster. On the AFTER flow, you may also see the name of the resolved queue, depending on the options specified in the MQOPEN by the application.

Note the information from the product documentation about what gets returned in the resolved object fields:

When you open a remote queue or cluster queue, the ResolvedQName and ResolvedQMgrName fields of the MQOD structure are filled with the names of the remote queue and remote queue manager found in the remote queue definition, or with the chosen remote cluster queue. Use the MQOO_RESOLVE_LOCAL_Q option of the MQOPEN call to fill the ResolvedQName in the MQOD structure with the name of the local queue that was opened. The ResolvedQMgrName is similarly filled with the name of the local queue manager hosting the local queue. This field is available only with Version 3 of the MQOD structure; if the structure is less than Version 3, MQOO_RESOLVE_LOCAL_Q is ignored without an error being returned. If you specify MQOO_RESOLVE_LOCAL_Q when opening, for example, a remote queue, ResolvedQName is the name of the transmission queue to which messages will be put. ResolvedQMgrName is the name of the local queue manager hosting the transmission queue.

Code such as

if ((*ppObjDesc)->Version >= MQOD_VERSION_3)
   memcpy(name, (*ppObjDesc)->ResolvedQName, MQ_Q_NAME_LENGTH);
else
   memcpy(name, (*ppObjDesc)->ObjectName, MQ_Q_NAME_LENGTH);

might give you the resolved name of interest, but you should try to be sensitive to the flags given in the ppPutMsgOpts field, depending on which level of resolution you want.

You will also see in the MQOD ResolvedType field an indication of whether the MQOPEN resulted in a queue or topic being opened (for example in the case of an alias queue pointing to a topic). Since the ResolvedType field was added in the MQOD version 4, you may need to temporarily swap out the MQOD supplied by the application with your own copy if the app is passing a lower level of the structure.

Similar processing gives you the real name of a queue that is being used by an MQPUT1. There’s no hObj in that verb because the object is only used for a single-shot operation, so it’s not something you might want to stash for later operations, but there is the same MQOD that you have access to in MQOPEN.

How do I know if a message has been retrieved because of a subscription? From which queue?

If you want to know that an MQGET is a result of a publication arriving at a queue, then you’d need to monitor the MQSUB calls to know the hObj in use for the MQGET. That object handle may have been returned from a previous MQOPEN. For managed subscription handles, there will be a real queue assigned but you will not have seen an MQOPEN for it and only get the hObj returned. However, that hObj is opened with permissions for you to use it in an MQINQ so it is possible to call that from within the exit if you really want to know the name of the dynamic queue.

Does an exit see every put/get of messages?

You see all the verbs issued by applications, so that includes all normal puts and gets. But the exit is not called for internally-generated messages put by the queue manager like triggers or events.

What about streaming queues?

The streaming queues feature is administratively-managed behaviour. It’s not something that applications explicitly invoke. There are no MQI calls or options that affect how streaming queues work. So you don’t see the “second” MQPUT in an API Exit. It’s all handled internally within the queue manager, much like the delivery of publications to multiple subscriber queues.

Obviously any direct access of the secondary queue, such as when a application pulls the messages off for processing, will be done through the normal MQI and hence visible to the API Exit.

Are there any special considerations when opening a topic?

Opening a topic is much the same as opening a queue. But you may need to look at both the ObjectName and the ObjectString fields in the MQOD. Either or both of the values may be set.

If someone PUTs to a topic, how does the exit know which queues the message is published to?

Basically it does not and cannot know. At least, not using this interface. The fan-out is done inside the queue manager during the MQPUT. Just as applications do not know about subscribers, neither does the exit.

A different exit point, the Publish Exit, does exist. That exit is called once for every message that is being delivered to a subscriber’s queue and allows modification of the message and manipulation of the target queue. But it

  • Is running inside the queue manager core processes so much more dangerous in failures
  • More performance-sensitive
  • Only one exit can be configured for a queue manager

Look at the sample exit amqspse0.c source and the associated structure definitions in cmqxc.h to see some of what it can do.

There is no linkage between an API Exit and the Publish Exit. They are completely separate exits, running in different process spaces and
at different times in the overall flow.

Do GET and PUT support more than 1 message per invocation?

MQPUT/MQPUT1/MQGET only deal with a single message at a time. As does MQCALLBACK.

What is the difference between BufferLength and pDataLength in the MQGET processing? Which is best used to measure message payload size?

BufferLength is the space provided by the application into which message data can be stored. DataLength is the actual message length that is being returned and can be no longer than the BufferLength.

What happens if there is not enough space in a buffer for the message data?

In normal MQI processing, the application will see an MQRC_TRUNCATED_MESSAGE_ACCEPTED (with MQCC_WARNING) or
MQRC_TRUNCATED_MESSAGE_FAILED (also with MQCC_WARNING) depending on its options. The exit will also see the same return values.

Where it gets a little tricky is if the exit is further manipulating the message content and buffers. There’s no clear rule that should be applied for exit design, but remember that once the queue manager has returned MQCC_OK or (in most cases) MQCC_WARNING then the message has been removed from the queue – if an exit then goes on to indicate that the message retrieval has failed because it could not fit the exit-modified data in the application’s buffer, then the application cannot retry the MQGET to the queue manager. Remember too that if an application has failed to get a message because the buffer is too small and then retries the MQGET, the next-returned message might not be the same one as previously seen. Another application using the same queue (assuming there is non-exclusive input) might have already successfully removed the message and now you cannot rely on any knowledge of the failed buffer sizes to determine how to proceed.

You should not attempt to expand (and then return) the application-provided buffer during MQGET. You have no way of knowing how the buffer was allocated or if/when it will be freed by the application. Replacing the application’s buffers with your own could lead to memory corruption or leaks. You can, of course, replace the buffer temporarily during the GetBefore operation provided you restore the original buffer during GetAfter. Though I’ll repeat that you need to consider what happens with truncations – the message might fit in your modified buffer, but not fit in the application-supplied buffer.

If you need to indicate that the retrieval has failed because of truncation reasons then you may be able to  hint at the length of buffer required as many applications will use that hint to resize buffers on a retry. For example,

if (pGMO->Version >= MQGMO_VERSION_3)
  pGMO->ReturnedLength = <required length>

Applications may be checking returned buffer sizes. One specific application that does do that is the MQ MCA process. If it sees that you have tried to expand the returned buffer beyond what it has provided, then it will generate an FDC and end. The FDC will probably have a probeid CO007003 from function ccxSend, and the error is length.

If the application is using transactions, then in theory you could force an MQBACK to restore the message to the queue but it’s not normally a good idea to interfere with the application’s view of transaction boundaries.

An additional idea on how to handle truncation by the exit is to temporarily stash a copy of the complete message data. If the application then attempts another MQGET with a larger buffer for the same queue, then the exit can use its GetBefore function to return the full message body directly to the application. It should also set the pExitParms->ExitResponse field to MQXCC_SKIP_FUNCTION so the real MQGET does not happen at all. That response code bypasses the underlying MQI function. You may also want to set pExitParms->ExitResponse2 to MQXR2_DEFAULT_CONTINUATION. The GetAfter function will still be called if it is configured, so you would want to ensure that the fake response doesn’t confuse the GetAfter routine. Or alternatively, do just the SKIP in the GetBefore, and return the message data in the GetAfter. Either way works.

Can you give more details about WARNINGs during an MQGET?

There are some return codes associated with MQCC_WARNING where a message has not actually been removed from the queue, most notably MQRC_TRUNCATED_MESSAGE_FAILED. But apart from that specific value, if I cared about the message body, I’d probably compare the MQMD from the GetBefore function with the MQMD available in the GetAfter. If the two structures are different, then a message has indeed been extracted.

When do the Before and After calls for MQ CallBack occur in relation to the client application?

For a regular MQGET with data conversion the API Exit gets:

  - Function=MQXF_GET, ExitReason=Before
  - Function=MQXF_DATA_CONV_ON_GET, ExitReason=Before
  - Function=MQXF_GET, ExitReason=After

For CallBack with data conversion, the API Exit gets:

 - Function=MQXF_DATA_CONV_ON_GET, ExitReason=Before
 - Function=MQXF_CALLBACK, ExitReason=Before
 - Function=MQXF_CALLBACK, ExitReason=After

For MQCallBack without data conversion, the API Exit gets:

 - Function=MQXF_CALLBACK, ExitReason=Before
 - Function=MQXF_CALLBACK, ExitReason=After

The ordering of these flows for Callback seems odd

It may seem strange that the data conversion call is done first for a Callback when it is between the BEFORE and AFTER in the synchronous MQGET case. But realise that when you are using callbacks, the message has been removed from the queue before the callback function is invoked. It’s logically pushed into the app, rather than the synchronous pull in MQGET processing.

So the logic works that the BEFORE phase needs to be given the message data, unlike during MQGET where you’d see that in the AFTER phase. And the data conversion point is also done before the user’s callback function is invoked, so the API Exit can see both unconverted, and then converted, message data – potentially changing them before the user’s function sees it.

Think of it like

* MQGET --> Exit(MQXF_GET/BEFORE) --> qmgr
*                                     message passed to app process
*      <-- Exit(MQXF_DATA_CONV)
*             data converted
*      <-- Exit(MQXF_GET/AFTER)

MQGET completes with message contents

With callbacks …

* qmgr --> message pushed to application process --> Exit(MQXF_DATA_CONV)
*        data converted --> Exit(MQXF_CALLBACK/BEFORE)
*                       --> user's callback function invoked
*                       <-- Exit(MQXF_CALLBACK/AFTER)

How can I investigate further into when and how exits are called?

Traces of the application process will show the flow – look for functions starting with zutCallApiExits. While tracing, in general, is intended for IBM service to look at, there’s often a reasonable amount of information you can deduce from looking through one.

Message Properties

One thing I’ve started to see more of is the use of exits to manipulate message properties. A common idea is that a property gets added to a message by the exit, for inspection and reporting by further instances of the exit running in other applications on other queue managers. It enables things like performance monitoring and tracking of application end-to-end flows. The property is then ideally removed from the message before the intended application sees it.

There is no ideal design for these situations. There can be all kinds of pathological cases, with different application designs and system configurations, but it does work well-enough for the vast majority of real-world topologies and architectures.

A sample exit amqsaem is shipped with MQ to show some ways of working with properties.

With that in mind, here are some specific questions and comments I’ve received about working with properties in API Exits.

My exit usually reads some values from the MQMD but occasionally, the MQMD is NULL. Is this correct? How do I proceed?

It is possible for the MQMD for a message to be NULL on entry to the MQPUT intercept point. One way to demonstrate this is to use the amqsstm sample program that shows how to set message properties.

For example

  echo  "prop1\ngreen\n\nMy message body\n" | /opt/mqm/samp/bin/amqsstm QUEUE1 QMA

To see the effect, you can check for null on PutBefore and PutAfter intercept points:

  if (ppMsgDesc == NULL || *ppMsgDesc == NULL) {
     // print to log
  }

If you browse the message on the queue, it has a fully-fledged MQMD.

This happens because amqsstm does explicitly set the MQMD to NULL. It’s making use of a very unusual case (and not something I’ve ever seen used in regular applications) where the MQMD is essentially held instead in message properties. The idea is that an app can easily copy an inbound message+MQMD to outbound while just providing deltas to properties including the MQMD.

So if the MQMD is NULL, you should be able to find the equivalent information in properties such as “Root.MQMD.Format” from one of the message handle references passed to the exit in the MQPMO structure just as it has been set by amqsstm. If a property is not discoverable through any of the message handles, then it would have the default value associated with the MQMD structure.

If both NewMsgHandle and OriginalMsgHandle are set, then properties in the NewMsgHandle take precedence over the same-named property in OriginalMsgHandle.

If two messages are put, one put using a named property, and another put with an RFH2 folder, will they look identical on a binary level?

A message that was put using an RFH2 to store properties, or put using MQSETMP properties, or even with a hybrid of both, will be processed the same internally. The receving program doing an MQGET has no idea how the properties of the message were originally set. The queue manager is even at liberty to do things like combine chained RFH2s into a single block for storage and onward transmission.

If the application has not used properties or RFH2 when putting the message, can (or should) an exit add its own properties.

The answer here really depends on whether you want to add a new property
that is only visible to instances of your exit, or whether you want to use a property that could be visible to application code.

An exit can register a folder name during its initialisation that can be
used for dedicated properties. Assuming that the same exit is used both
for PUT and GET applications, then any properties belonging to that folder and added to the message during the Put phase will be visible only to an exit that has registered use of the same folder during Get. No matter how the application retrieves the message (or settings on the PROPCTL attribute of the queue), then those properties will not be seen in the application.

If the exit may sometimes be modifying properties created outside of the exit, then it would not need to register a folder and instead would likely be referring to something in the usr folder. While the exit can add the property if it doesn’t exist, there is a potential issue that applications that are not expecting to see any properties in the message will now receive it either as a property or in an RFH2 header. Which might break the application. The PROPCTL attribute can be used to always strip properties before the application sees them but that may not be feasible to enforce. An exit may need some kind of configurable policy on whether to insert properties into application-level folders if they are not already in there.

Why is the exit not seeing properties for its registered folder?

There is one very odd piece of behaviour regarding a folder that an exit registers for its own properties. If you want to do this, then you MUST have GetBefore and CbBefore (for the MQCB function) interception functions implemented by your exit. Those functions can be essentially empty, with just a return line in them, but they must exist. If they do not, then the queue manager does not correctly deal with filling in the ExitHandle references to properties. The exit-added properties are then either discarded or returned as regular properties to the application.

Any update to this behaviour to not require these interception points would of course only be available in future MQ versions – it was in fact corrected in MQ 9.3.2. But exits that want to work with previous versions will need this workround anyway.

How should I work with message properties created by an MQ Client application? Can I tell if I’m running inside a client environment?

As has already been mentioned, API Exits can be used inside client programs that are using the C MQI libraries. That rules out trying to run directly inside Java or managed .Net processes. But there is an additional restriction around processing message properties: trying to register use of an API Exit-owned property folder by setting an MQXEPO structure will fail with MQRC_EXIT_PROPS_NOT_SUPPORTED (2588).

As you probably want to work with properties regardless of the client program’s language and environment, then you can intercept the SVRCONN work done on the app’s behalf, and that will work fine. But you may still find your exit being loaded in C client programs, and you probably want to avoid double processing.

A program may or may not have provided an MQCD to the CONNX verb; some environment variables such as MQ_CONNECT_TYPE might be set, but these are not guaranteed to always be there. Instead we can actually make use of this MQRC failure.

I have written code like this:

MQXEPO ExitOpts = {MQXEPO_DEFAULT}; // Might fill this in further
MQBOOL clientMode = FALSE;

pExitParms->Hconfig->MQXEP_Call( pExitParms->Hconfig,
MQXR_CONNECTION, MQXF_INIT,
(PMQFUNC) InitialiseConn,
&ExitOpts,
pCompCode, pReason);
if (*pReason == MQRC_EXIT_PROPS_NOT_SUPPORTED)
{
// Must be running in client mode - try again without the XEPO struc
// And perhaps use "clientMode" to modify which functions to intercept
clientMode = TRUE;
pExitParms->Hconfig->MQXEP_Call ( pExitParms->Hconfig,
MQXR_CONNECTION, MQXF_INIT,
(PMQFUNC) InitialiseConn,
NULL,
pCompCode, pReason);
}

I have recently discovered an alternative flag that DOES indicate the environment. The pExitParms->Hconfig->Flags field has bitfield settings that indicate client/server and threaded/non-threaded options. You can find these flags in cmqec.h. For example:

printf("%s\n",flags & MQIEPF_LOCAL_LIBRARY?"Local":"Client");

We are trying to use PutBefore to change a message property value that will be put to the queue. How should this work?

In general, an exit can modify an MQPUT with a named property or an RFH2 key-value tag. It is probably easiest to use the MQSETMP approach rather than modifying user-supplied buffers with an RFH2.

If the application is already using properties and there is a message handle passed in, then the exit can exploit that. If there is no
message handle, any existing property will have been set with the RFH2 structure. The exit can use the MQBUFMH verb to convert an RFH2-style message, removing the RFH2 and attaching the properties to a handle which can then be queried or modified. Using the MQBUFMH/MQMHBUF verbs can be expensive, so don’t use them unless necessary.

Error handling and debugging

Having written an exit, it needs to be tested and debugged. This section discusses some of the ways in which that might be done. Along with some exit-specific information, all the usual good practices around C coding should be followed. For example, check that malloc is not returning NULL; remember that you might be in a threaded environment so you might need locking around any global variables; free any allocated buffers when they are no longer required etc.

How can I track behaviour of my exit

There are several different approaches that you can take when trying to diagnose problems and determine what your exit is doing. The choice of which mechanism to use may depend on the environment in which your exit is running.

There are essentially two ways in which you might be running programs to exercise and test an exit:

  • Running directly inside an application. Here, the exit can usually do things like printing messages to stdout. While you would not expect to do that in a “production” exit, it might be appropriate to have an optional debug trace.
  • Running inside a queue manager process, including the SVRCONN. Here, you will not normally have access to stdout. Any debug information will have to be reported using some other mechanism.

One approach I often take is to always initialise a FILE pointer when debug is required, regardless of the environment. But set it to stdout whenever I can. For example:

if (getenv("APIX_DEBUG") {
if (isatty(fileno(stdout))) fp = stdout;
else fp = fopen(debugFile,"a+");
}
if (fp)
fprintf(fp,"APIX initialised\n");

That file can then be used for printing any debug or trace information that I think I need.

Can I use a source-level debugger

Yes. When your application runs in the foreground, then a source debugger can usually be applied directly. Breakpoints can be set to the name of a function in your exit, even before the program starts and before the exit is loaded.


$ gdb bin/get0
GNU gdb (GDB) Fedora 10.2-3.fc34
Copyright (C) 2021 Free Software Foundation, Inc.
Reading symbols from bin/get0...
(gdb) break GetBefore
Function "GetBefore" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (GetBefore) pending.
(gdb) run Q1 QM1
Starting program: /apix/bin/get0 Q1 QM1
Missing separate debuginfo for /usr/lib64/libmqm_r.so

Sample AMQSGET0 start

Breakpoint 1, GetBefore (pExitParms=0x7ffffffebff0, pExitContext=0x7ffffffec0f0, pHconn=0x7ffffffec37c, pHobj=0x7ffffffec378, ppMsgDesc=0x7ffffffec370, ppGetMsgOpts=0x7ffffffec368,
pBufferLength=0x7ffffffec364, ppBuffer=0x7ffffffec358, ppDataLength=0x7ffffffec590, pCompCode=0x7fffffffc824, pReason=0x7fffffffc81c) at apix.c:224
224 PMQMD pMQMD = *ppMsgDesc;

(gdb)

When you are trying to debug an exit that is running inside a queue manager process such as the MCA, then you might need to use the debugger’s ability to attach to an existing process. And you may also need to be running with root authority as the processes are typically setuid.

Obviously you will not have any useful access to the queue manager library symbols, but once inside your exit code, all the usual power of the debugger should be available to you. Though remember to compile your exit in a suitable fashion (typically the -g flag to gcc).

What access do I have to MQ’s reporting services

An exit cannot call any of MQ’s trace/error services. If you want to report a fatal error, you have to do it in your own way rather than being able to create an MQ FDC file. Similarly you cannot add your own function information to the MQ trace output.

But there are a few things that can help within the MQ trace. Firstly, all calls to the exit are reported. While the queue manager cannot trace within the exit, it does still note that it is either about to call or has just returned from a call.

Secondly, an exit does have a small buffer available from parameters passed to it, ExitPDArea, that does get traced after returning. I normally use that for just reporting the most basic details:

sprintf(buf,"Build:%s %s Program:%s", 
__DATE__,__TIME__,pExitContext->ApplName);
strncpy(pExitParms->ExitPDArea,buf,sizeof(pExitParms->ExitPDArea));

In the MQ trace, you can see the fact that the exit has been invoked, its return values, and the filled-in content of this buffer:

--{ zutCallApiExitsBeforePut
---{ xcsReallocMemFn
----{ xcsGetMemFn
----} xcsGetMemFn rc=OK FunctionTime=8
---} xcsReallocMemFn rc=OK FunctionTime=12
---{ xcsGetEnvironmentInteger
----{ xcsGetEnvironmentString
xcsGetEnvironmentString[AMQ_VERIFY_PROPERTIES] = NULL
----}! xcsGetEnvironmentString rc=xecE_E_ENV_VAR_NOT_FOUND FunctionTime=3
---}! xcsGetEnvironmentInteger rc=xecE_E_ENV_VAR_NOT_FOUND FunctionTime=6
---{ APIExit
ApiExit->Name:MarkAPIX ExitReason:1, Function:9
CompCode:0 Reason:0 ExitResponse:0 ExitResponse2:0
---} APIExit rc=OK FunctionTime=15
ExitParms.ExitPDArea
0x0000: 4275696c 643a4f63 74203139 20323032 |Build:Oct 19 202|
0x0010: 31203134 3a35363a 31332050 726f6772 |1 14:56:13 Progr|
0x0020: 616d3a73 70757400 93610020 00000000 |am:sput..a. ....|
--} zutCallApiExitsBeforePut rc=OK FunctionTime=41

What protection does the queue manager provide for a broken exit

The queue manager does attempt to recover from aberrant exits. For example, it sets up signal handlers and a setjmp/longjmp pair around calls to exit functions.

I wrote an exit that deliberately causes a SEGV and an FFST was created. I’ve shown some of the header block below. The channel ended, but the queue manager survived:

Probe Id :- XC130004 
Component :- xehExceptionHandler
Source filename :- /build/slot2/p920_P/src/lib/cs/unix/amqxerrx.c
Program Name :- amqrmppa
Arguments :- -m APIX92A
Process :- 23018
Process(Thread) :- 23021
Thread :- 4 RemoteResponder
Arith1 :- 11 (0xb)
Comment1 :- SIGSEGV: address not mapped((nil))


O/S Call Stack for current thread
...
/usr/lib64/libpthread.so.0(+0x13a20)[0x7f1b04edca20]
/usr/lib64/libc.so.6(+0x163c00)[0x7f1b04e56c00]
/var/mqm/exits64/Installation1/apix(+0x57189)[0x7f1b0272d189]
/opt/mqm/lib64/libmqxzu_r.so(zutCallApiExitsBeforePut+0x75)[0x7f1b0515f965]
/opt/mqm/lib64/libmqzi_r.so(zstMQPUT+0x13e8)[0x7f1b05e51218]
/opt/mqm/lib64/libmqds_r.so(MQPUT+0xcd)[0x7f1b05bc78ed]
...


MQM Function Stack
...
MQPUT
zstMQPUT
zutCallApiExitsBeforePut
APIExit
xcsFFST

Other errors might have slightly different symptoms.

For example, I changed the bad exit to call abort() instead of creating the SEGV. When the exit was invoked from a SVRCONN environment, there was still an FFST and the queue manager continued to work after a fashion. But the FFST could only tell us that another program had ended unexpectedly. The health-checking that goes on between the different processes that make up a queue manager and its channels could tell there had been a problem with amqrmppa (the program that hosts SVRCONNs), but the failing program itself had gone without an FDC trace:

Program Name :- runmqlsr
Thread :- 3 JobMonitor
Major Errorcode :- zrcX_NON_CRITICAL_PROCESS_MISSING
Minor Errorcode :- OK
Probe Type :- MSGAMQ5053
Probe Severity :- 3
Probe Description :-
AMQ5053W: IBM MQ process 23673 (amqrmppa) cannot be
found and is assumed to be terminated.
FDCSequenceNumber :- 0
Arith1 :- 23673 (0x5c79)
Arith2 :- 65280 (0xff00)
Comment1 :- amqrmppa

As I had MQ trace running at the same time, I could also see an abrupt end to the trace file for the amqrmppa procress, which does at least tell us that it was very likely the exit that caused the failure:

-------{ zutCallApiExitsBeforePut
Initialising properties to default values
--------{ APIExit
ApiExit->Name:MarkAPIX ExitReason:1, Function:9
===========================================================
[trace output ended here!]

In this situation, another way would have to be found to diagnose and solve the problem.

Conclusion

I’ve tried to summarise a number of questions from software developers about API Exits. While investigating some of the more obscure features particularly around properties that people were asking about, I found it very useful to have a skeleton API Exit that I could modify quickly, and Makefiles and scripts to drive simple tests. I don’t think I wrote any application code for these tests – the standard samples (amqsput, amqsget, amqsbcg) and amqsstm seemed to be all that I needed. Don’t forget that one of the parameters to amqsbcg tells it whether to show properties or use RFH2 formatting for the messages it browses.

I will try to update this article if I receive further questions in this area. I hope this is useful.

Update History
  • 2020-10-01: added example of bad programming around structure versions
  • 2020-10-16: added example of how you may not be able to use the results of one MQI verb as direct input to another operation
  • 2020-11-17: some clarifications based on feedback
  • 2020-12-18: expand on how exits run inside the application process
  • 2021-05-11: notes about properties and clients and determining environment
  • 2021-05-18: notes about MQI calls seen by the exit even when not directly issued by the client application
  • 2021-10-18: expand on MQGET trunction and buffer manipulation; added anchor links
  • 2021-10-19: new section on error handling and debugging
  • 2022-01-14: MQGET warning codes
  • 2022-05-03: Determining if exit was loaded
  • 2022-06-23: Using threads in an exit; more about how client programs appear inside the SVRCONN process
  • 2022-11-01: Note on needing CbBefore/GetBefore if exits want to register their own folder
  • 2023-02-09: Adding comment on streaming queues
  • 2023-11-30: Updated links to product documentation. Comment about SKIP option
  • 2024-10-07: Added init/term lifecycle details

This post was last updated on October 7th, 2024 at 10:31 am

Leave a Reply

Your email address will not be published. Required fields are marked *