You can perform assured message delivery using synchronous message transmission, but the application must take responsibility for error handling.
Non-assured delivery of a message takes place in a single network flow. The queue manager sending the message creates or reuses a connection to the destination queue manager.
The message to be sent is dumped to create a byte-stream, and this byte stream is given to the connection for transmission. Once program control has returned from the connection, the sender queue manager knows that the message has been successfully given to the target queue manager, that the target has logged the message on a queue, and that the message has been made visible to WebSphere MQ Everyplace applications.
However, a problem can occur if the sender receives an exception over the connection from the target. The sender has no way of knowing if the exception occurred before or after the message was logged and made visible. If the exception occurred before the message was made visible it is safe for the sender to send the message again. However, if the exception occurred after the message was made visible, there is a danger of introducing duplicate messages into the system since an WebSphere MQ Everyplace application could have processed the message before it was sent the second time.
The solution to this problem involves transmitting an additional confirmation flow. If the sender application receives a successful response to this flow, then it knows that the message has been delivered once and once-only.
The confirmId parameter of the mqeQueueManager_putMessage function dictates whether the confirm flow is sent or not. A value of zero means that message transmission occurs in one flow, while a value of greater than zero means that a confirm flow is expected. The target queue manager logs the message to the destination queue as usual, but the message is locked and invisible to WebSphere MQ Everyplace applications, until the confirm flow is received.
An WebSphere MQ Everyplace application can issue a put message confirmation using the mqeQueueManager_confirmPutMessage function. Once the target queue manager receives the flow generated by this command, it unlocks the message, and makes it visible to WebSphere MQ Everyplace applications. You can confirm only one message at a time, it is not possible to confirm a batch of messages.
Figure 10. Assured put of synchronous messages
The mqeQueueManager_confirmPutMessage() function requires you to specify the UID of the message, not the Confirm ID used in the prior put message command. The Confirm ID is used to restore messages that remain locked after a transmission failure. This is explained in detail on page ***.
A skeleton version of the code required for an assured put is shown below:
MQEINT64 confirmID; rc = mqe_uniqueValue(pExceptBlock,&confirmID); if (MQERETURN_OK == rc) { rc = mqeQueueManager_putMessage(hQmgrHandle, pExceptBlock, hQueueManagerName, hQueueName,hOutMsg,NULL, confirmID; if (MQERETURN_OK == rc) { MQeFieldsHndl hFilter; rc = mqeMsg_getMsgUIDFields(hOutMsg, pExceptBlock, &hFilter); /* ... ideally check error here */ rc = mqeQueueManaber_confirmPutMessage(hQMgrHandle, pExceptBlock, hQueueManagerName, hQueueName, hFilter} if (MQERETURN_OK == rc) { } else { /* ... error checking here */} } else {/* ... error checking here */ } } else { /* ... error checking here */ }
If a failure occurs during step 1 in Figure 10 the application should retransmit the message. There is no danger of introducing duplicate messages into the WebSphere MQ Everyplace network since the message at the target queue manager is not made visible to applications until the confirm flow has been successfully processed.
If the WebSphere MQ Everyplace application retransmits the message, it should also inform the target queue manager that this is happening. The target queue manager deletes any duplicate copy of the message that it already has. The application sets the MQE_MSG_RESEND field to do this.
If a failure occurs during step 2 in Figure 10 the application should send the confirm flow again. There is no danger in doing this since the target queue manager ignores any confirm flows it receives for messages that it has already confirmed. This is shown in the following example.
MQEBOOL msgPut = MQE_FALSE; /* put message successful? */ MQEBOOL putConfirmed = MQE_FALSE; /* put confirm successful? */ MQEINT64 confirmId; MQERETURN rc; /* generate a confirm Id */ rc = mqe_uniqueValue(pErrorBlock, &confirmID); if (MQERETURN_OK == rc) { MQEINT32 retry; /* try to put message - retry if there are problems until maximum retry*/ /* count is exceeded */ for (retry=0; (MQE_FALSE == msgPut) && (retry < MAX_RETRY); retry++ ) { rc = mqeQueueManager_putMessage(hQMgr, pErrorBlock, hQueueManager, hQueue, hOutMsg, NULL, confirmId ); if (MQERETURN_OK == rc) { /* put successful */ msgPut = MQE_TRUE; } else { /* set the message resend flag */ /* (error block pointer set to NULL to avoid *pErrorBlock*/ /* being overwritten) */ rc = mqeFields_putBoolean((MQeFieldsHndl)hOutMsg, NULL, MQE_MSG_RESEND, MQE_TRUE ); if (MQERETURN_OK != rc) { printf("mqeFields_putBoolean error"); break; } } } if (MQE_FALSE == msgPut) { /* Number of retries has exceeded the maximum allowed, so abort the put */ /* message attempt */ /* Attempt to delete any copy of the message held on the target queue */ /* This operation may well fail */ /* if the message does exist on the target queue then it will remain */ /* there until it expires (if an expiry value has been set on either */ /* the message or queue. */ rc = mqeQueueManager_undo(hQMgr, NULL, hQueueManager, hQueue, confirmId); if (MQERETURN_OK != rc) { printf("mqeQueueManager_undo error"); } /* return with put error */ return pErrorBlock->ec; } else { MQeFieldsHndl hFilter; /* put message unsuccessful */ /* get message Unique ID for use on confirm put operation */ rc = mqeMsg_getMsgUIDFields(hOutMsg, pErrorBlock, &hFilter); if (MQERETURN_OK == rc) { /* try to confirm the previous put - retry if there are problems until */ /* maximum retry count is exceeded */ for (retry=0; (MQE_FALSE == putConfirmed) && (retry < MAX_RETRY); retry++){ rc = mqeQueueManager_confirmPutMessage(hQMgr, pErrorBlock, hQueueManager, hQueue, hFilter ); if ((MQERETURN_OK == rc) || (MQERETURN_J_NOT_FOUND == rc)) { /* confirm successful */ /* An MQERETURN_J_NOT_FOUND exception means that the message */ /* has already been confirmed */ putConfirmed = MQE_TRUE; } /* another type of exception - need to reconfirm message */ } if ( MQE_FALSE == putConfirmed ) { /* Attempt to undo any copy of the message held on the target queue */ /* This operation may well fail */ rc = mqeQueueManager_undo(hQMgr, NULL, hQueueManager, hQueue, confirmId); if (MQERETURN_OK != rc) { printf("mqeQueueManager_undo error"); } /* return with confirmPut error */ return pErrorBlock->ec; } (void)mqeFields_free(hFilter, NULL); } else { displayError("mqeMsg_getMsgUIDFields error", pErrorBlock); } } } else { displayError("mqe_uniqueValue error", pErrorBlock); }
Assured message get works in a similar way to put If a get message command is issued with a confirmId parameter greater than zero, the message is left locked on the queue on which it resides until a confirm flow is processed by the target queue manager. When a confirm flow is received, the message is deleted from the queue.
Figure 11. Assured get of synchronous messages
The value passed as the confirmId parameter also has another use. The value is used to identify the message while it is locked and awaiting confirmation. If an error occurs during the get operation, it can potentially leave the message locked on the queue. This happens if the message is locked in response to the get command, but an error occurs before the application receives the message. If the application reissues the get in response to the exception, then it will be unable to obtain the same message because it is locked and invisible to WebSphere MQ Everyplace applications.
However, the application that issued the get command can restore the messages using the undo function. The application must supply the confirmId value that it supplied to the get message command. The undo command restores messages to the state they were in before the get command.
MQEBOOL msgGet = MQE_FALSE; /* get message successful? */ MQEBOOL getConfirmed = MQE_FALSE; /* get confirm successful? */ MQEINT64 confirmId; MQERETURN rc; *phInMsg = NULL; /* generate a confirm Id */ rc = mqe_uniqueValue(pErrorBlock,&confirmID); if (MQERETURN_OK == rc) { MQEINT32 retry; /* try to put message - retry if there are problems until maximum retry */ /* count is exceeded */ for (retry=0; (MQE_FALSE == msgGet) && (retry < MAX_RETRY); retry++ ) { rc = mqeQueueManager_getMessage(hQMgr, pErrorBlock, phInMsg, hQueueManager, hQueue, hFilter, NULL, confirmId); if (MQERETURN_OK == rc) { /* put successful */ msgGet = MQE_TRUE; } else if (MQERETURN_J_Q_NO_MATCHING_MSG == rc) { /* the message is unavailable */ break; } else { /* As a precaution, undo the message on the queue. This will remove */ /* any lock that may have been put on the message prior to the */ /* exception occurring */ rc = mqeQueueManager_undo(hQMgr, NULL, hQueueManager, hQueue, confirmId); if (MQERETURN_OK != rc) { printf("mqeQueueManager_undo error"); } } } if (MQE_FALSE == msgGet) { /* Number of retry attempts has exceeded the maximum allowed, so abort */ /* get message operation */ return pErrorBlock->ec; } else { /* try to confirm the previous get message operation - retry if there are */ /* problems until maximum retry count is exceeded */ for (retry=0; (MQE_FALSE == getConfirmed) && (retry < MAX_RETRY); retry++) { rc = mqeQueueManager_confirmGetMessage(hQMgr, pErrorBlock, hQueueManager, hQueue, hFilter); if (MQERETURN_OK == rc) { getConfirmed = MQE_TRUE; } } if ( MQE_FALSE == getConfirmed ) { /* need to free the message already got and return error */ (void)mqeMsg_free(*phInMsg, NULL); return pErrorBlock->ec; } } } else { displayError("mqe_uniqueValue error", pErrorBlock); }
The undo command also has relevance for the mqeQueueManager_putMessage and mqeQueueManager_browseMessagesAndLock commands. As with get message, the undo command restores any messages locked by the mqeQueueManager_browseMessagesandLock command to their previous state.
If an application issues an undo command after a failed mqeQueueManager_putMessage command, then any message locked on the target queue awaiting confirmation is deleted.
The undo command works for operations on both local and remote queues.