ibm-messaging / mq-mqi-nodejs

Calling IBM MQ from Node.js - a JavaScript MQI wrapper
Apache License 2.0
79 stars 41 forks source link

MQRC_CONNECTION_BROKEN error is not received in get callback #173

Closed revathskumar closed 6 months ago

revathskumar commented 10 months ago

Please include the following information in your ticket.

2.0.2

samples/amqsgeta.js (with minor changes to options)

```diff 'use strict'; /* Copyright (c) IBM Corporation 2023 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Contributors: Mark Taylor - Initial Contribution */ /* * This is an example of a Node.js program to get messages from an IBM MQ * queue using an asynchronous method. * * The queue and queue manager name can be given as parameters on the * command line. Defaults are coded in the program. * * Each MQI call prints its success or failure. */ // Import the MQ package // const mq = require('ibmmq'); import * as mq from 'ibmmq'; import { MQC } from 'ibmmq'; // Want to refer to this export directly for simplicity // import { resolve } from 'path'; // const MQC = mq.MQC; // Want to refer to this export directly for simplicity // Import any other packages needed const StringDecoder = require('string_decoder').StringDecoder; const decoder = new StringDecoder('utf8'); // The default queue manager and queue to be used const qMgr = 'QM1'; const qName = 'DEV.QUEUE.1'; const msgId = null; // Some global variables let connectionHandle; let queueHandle; const waitInterval = 3; // max seconds to wait for a new message let ok = true; let exitCode = 0; /* * Format any error messages */ function formatErr(err) { if (err) { ok = false; return 'MQ call failed at ' + err.message; } else { return 'MQ call successful'; } } function hexToBytes(hex) { for (var bytes = [], c = 0; c < hex.length; c += 2) bytes.push(parseInt(hex.substr(c, 2), 16)); return bytes; } /* * Define which messages we want to get, and how. */ function getMessages() { const md = new mq.MQMD(); const gmo = new mq.MQGMO(); gmo.Options = + MQC.MQGMO_NO_SYNCPOINT | + MQC.MQGMO_NO_WAIT | MQC.MQGMO_CONVERT | MQC.MQGMO_FAIL_IF_QUIESCING; gmo.MatchOptions = MQC.MQMO_NONE; - // gmo.WaitInterval = waitInterval * 1000; // 3 seconds - // if (msgId != null) { - // console.log('Setting Match Option for MsgId'); - // gmo.MatchOptions = MQC.MQMO_MATCH_MSG_ID; - // md.MsgId = hexToBytes(msgId); - // } // Set up the callback handler to be invoked when there // are any incoming messages. mq.Get(queueHandle, md, gmo, getCB); // And now enable the callback mq.Ctl(connectionHandle, MQC.MQOP_START, function (err) { console.log('Ctl:CB :: ', err); console.log(formatErr(err)); }); } /* * This function is the async callback. Parameters * include the message descriptor and the buffer containing * the message data. */ function getCB(err, hObj, gmo, md, buf, hConn) { console.log('getCB :: err :: ', err); // If there is an error, prepare to exit by setting the ok flag to false. if (err) { if (err.mqrc == MQC.MQRC_NO_MSG_AVAILABLE) { console.log('No more messages available.'); } else { console.log(formatErr(err)); exitCode = 1; } ok = false; // We don't need any more messages delivered, so cause the // callback to be deleted after this one has completed. // mq.GetDone(hObj); } else { if (md.Format == 'MQSTR') { console.log('message <%s>', decoder.write(buf)); } else { console.log('binary message: ' + buf); } // If we were using Syncpoint, then we may want to commit the updates. if ((gmo.Options & MQC.MQGMO_SYNCPOINT) != 0) { mq.Cmit(connectionHandle, function (err) { if (err) console.log(formatErr(err)); else console.log('Commit OK'); }); } } } /* * When we're done, close any queues and connections. */ function cleanup(hConn, hObj) { mq.Close(hObj, 0, function (err) { if (err) { console.log(formatErr(err)); } else { console.log('MQCLOSE successful'); } mq.Disc(hConn, function (err) { if (err) { console.log(formatErr(err)); } else { console.log('MQDISC successful'); } }); }); } /************************************************************** * The program really starts here. * Connect to the queue manager. If that works, the callback function * opens the queue, and then we can start to retrieve messages. */ console.log('Sample AMQSGETA.JS start', qMgr); // Get command line parameters // const myArgs = process.argv.slice(2); // Remove redundant parms // if (myArgs[0]) { // qName = myArgs[0]; // } // if (myArgs[1]) { // qMgr = myArgs[1]; // } // if (myArgs[2]) { // msgId = myArgs[2]; // } + const connectionDescriptor = new mq.MQCD(); + connectionDescriptor.ConnectionName = `localhost(1414)`; + connectionDescriptor.ChannelName = 'DEV.APP.SVRCONN'; + const cno = new mq.MQCNO(); + cno.ClientConn = connectionDescriptor; // cno.Options = MQC.MQCNO_RECONNECT; // Connect to the queue manager, including a callback function for // when it completes. mq.Connx(qMgr, cno, function (err, hConn) { if (err) { console.log('mq.Connx :: ', err); console.log(formatErr(err)); ok = false; } else { console.log('MQCONN to %s successful ', qMgr); connectionHandle = hConn; // Define what we want to open, and how we want to open it. const od = new mq.MQOD(); od.ObjectName = qName; od.ObjectType = MQC.MQOT_Q; + const openOptions = MQC.MQOO_INPUT_AS_Q_DEF | MQC.MQOO_OUTPUT; mq.Open(hConn, od, openOptions, function (err, hObj) { queueHandle = hObj; if (err) { console.log(formatErr(err)); } else { console.log('MQOPEN of %s successful', qName); // And now we can ask for the messages to be delivered. getMessages(); } }); } }); // We need to keep the program active so that the callbacks from the // message handler are processed. This is one way to do it. Use the // defined waitInterval with a bit extra added and look for the // current status. If not OK, then close everything down cleanly. // setInterval(function () { // if (!ok) { // console.log('Exiting ...'); // cleanup(connectionHandle, queueHandle); // process.exit(exitCode); // } // }, (waitInterval + 2) * 1000); ```

Logs

2x (UseCtl = false)

[mqnpi] 2023-12-21T15:12:27.341Z : Called the callback with no data.
[mqnpi] 2023-12-21T15:12:27.341Z : PreJsCB - MQCTL RESUME hConn=2113929221 CC=0 RC=0
[mqnpi] 2023-12-21T15:12:30.351Z : In mqnCB
[mqnpi] 2023-12-21T15:12:30.351Z : mqnCB - Error 2033 for hConn/hObj 2113929221/101
[mqnpi] 2023-12-21T15:12:30.362Z : mqnCB - MQCTL SUSPEND hConn=2113929221 CC=0 RC=0
[mqnpi] 2023-12-21T15:12:30.365Z : In PreJsCB
[ibmmq] 2023-12-21T15:12:30.366Z :   [MQError               ] GET[async]: MQCC = MQCC_FAILED [2] MQRC = MQRC_NO_MSG_AVAILABLE [2033]
[ibmmq] 2023-12-21T15:12:30.367Z : Pre AppCB
getCB :: err ::  MQError: GET[async]: MQCC = MQCC_FAILED [2] MQRC = MQRC_NO_MSG_AVAILABLE [2033]
    at preJsAppCB (/path/to/mqigeta.js) {
  mqcc: 2,
  mqccstr: 'MQCC_FAILED',
  mqrc: 2033,
  mqrcstr: 'MQRC_NO_MSG_AVAILABLE',
  version: '2.0.2',
  verb: 'GET[async]'
}
No more messages available.
[ibmmq] 2023-12-21T15:12:30.370Z : Post AppCB
[mqnpi] 2023-12-21T15:12:30.370Z : Called the callback with no data.
[mqnpi] 2023-12-21T15:12:30.370Z : PreJsCB - MQCTL RESUME hConn=2113929221 CC=0 RC=0
[mqnpi] 2023-12-21T15:12:33.306Z : In mqnCB
[mqnpi] 2023-12-21T15:12:33.306Z : mqnCB - Error 2009 for hConn/hObj 2113929221/0
[mqnpi] 2023-12-21T15:12:33.306Z : mqnCB - MQCTL SUSPEND hConn=2113929221 CC=2 RC=2009
[mqnpi] 2023-12-21T15:12:33.306Z : In PreJsCB
[ibmmq] 2023-12-21T15:12:33.306Z :   [MQError               ] GET[async]: MQCC = MQCC_FAILED [2] MQRC = MQRC_CONNECTION_BROKEN [2009]
[ibmmq] 2023-12-21T15:12:33.307Z : AppCB is null
[mqnpi] 2023-12-21T15:12:33.307Z : Called the callback with no data.
[mqnpi] 2023-12-21T15:12:33.307Z : PreJsCB - MQCTL RESUME hConn=2113929221 CC=2 RC=2009

In version 1.x we used to get the MQRC_CONNECTION_BROKEN in the get callback, along with messages which we use to reconnect when the connection to server is lost due to Queue manager restarting.

Now in version 2.x we don't receive this MQRC_CONNECTION_BROKEN in the get callback, due to this, when the connection is lost client won't be able to reconnect, and messages won't be received by client until we manually reset the connection (restarting the service).

Is this intended behaviour in version 2.0? Is there a better way to reconnect the client in such cases?

ibmmqmet commented 10 months ago

Thanks for the report.

I think I know what's going on - fundamentally there seems to be a difference in the underlying MQ code when connecting to a qmgr as client compared to using local bindings. The client is generating unexpected event callbacks. (I suspect an MQ client bug, but even if it gets fixed, I need to tolerate the existing behaviour.)

I know how I'll deal with it, and I expect to push a fix in the next package update.

revathskumar commented 7 months ago

@ibmmqmet From the 2.0.3 changelog and from basic testing, we are now receiving the events MQRC_CONNECTION_BROKEN.

So is it good to close this issue?

revathskumar commented 6 months ago

@ibmmqmet Thank You.