ibm-messaging / mq-golang

Calling IBM MQ from Go applications
Apache License 2.0
168 stars 60 forks source link

SSL and Multiple Connections - A question, maybe an issue #87

Closed sasbury closed 5 years ago

sasbury commented 5 years ago

Using latest version on master 7486f4a.

This is a question/issue so hopefully I provided enough context, and I apologize in advance for the long writeup.

I was able to get an SSL connection to work (for example in the amqsconn example I added):

cd.SSLCipherSpec = "TLS_RSA_WITH_AES_128_CBC_SHA256"
tlsParams := ibmmq.NewMQSCO()
tlsParams.KeyRepository = os.Args[4] <- this is a path to a "key/stl" combo
cno.SSLConfig = tlsParams

but if i try to make a second connection with the same settings I "sometimes" get MQRC_SSL_ALREADY_INITIALIZED. I don't always get the error, but generally if I try to connect after a wait I get an error, so maybe I sometimes sneak a connection in.

I don't know enough about what is happening under the covers to even guess if this is a bug or working as intended. So let me say what I am trying to do and the answer may be "you are doing it wrong".

My goal is to handle messages coming in on multiple queues transactionally. So if Q1 has a message I can handle that in a transaction and if Q2 has a message "at the same time" I can handle that in its own transaction. My reading of the various doc/code is that transactions for the go library are handled at the MQQueueManager level. So I was creating a MQQueueManager for each queue I want to handle using Connx. My code was working until I added SSL, when it started to fail with the MQRC_SSL_ALREADY_INITIALIZED error.

I am currently using callbacks for each Queue, but could use go routines and "gets" if that would help. But in all cases I don't see a way to share the MQQueueManager. Possibly the callbacks will never happen "at the same time" in which case perhaps I could safely use a single MQQueueManager across multiple queues with callbacks.

Any help wold be appreciated. I can't find enough info about how callbacks would be threaded, or why that error would happen on the latest versions. If there is a way to share the queue managers, the problem goes away, so I am open to a solution there as well.

sasbury commented 5 years ago

I updated the conn example to loop:

for i := 0; i < 15; i++ {
    fmt.Printf("Attempting to connect %d.\n", i)
    qmgr, err := ibmmq.Connx(qMgrName, cno)

    fmt.Printf("Got qmgr %v\n", qmgr)

    if err == nil {
        fmt.Printf("Connection to %s succeeded.\n", qMgrName)
        time.Sleep(time.Second)

and now i can go maybe 5-6 iterations before the error appears:

....
Attempting to connect 4.
Got qmgr {2113929229 QM1}
Connection to QM1 succeeded.
Attempting to connect 5.
Got qmgr {2113929231 QM1}
Connection to QM1 succeeded.
Attempting to connect 6.
Got qmgr {2113929233 }
Connection to QM1 failed.

I think the issue may be that the C lib will send this response even if there isn't a problem, but because the SSL is going to be reused from a previous connection. Or maybe there is a connection sharing effect and this is the first time we get to a totally new connection?

sasbury commented 5 years ago

Created a fix, tested manually with the example and in my code.

ibmmqmet commented 5 years ago

MQRC_SSL_ALREADY_INITIALIZED is explained at https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_9.1.0/com.ibm.mq.ref.dev.doc/q095520_.htm

Note that the CompCode is MQCC_WARNING, not ERROR. So the connection has not really failed to connect, but it just may not be configured as you might expect. I would not want that warning to be suppressed from applications. The application code should decide whether or not to continue based on the WARNING level.

As to why it's only happening on a few connections - I guess your SVRCONN has the default attribute of SHARECNV(10). The TLS communication is only set up on physical network connections - with that SHARECNV setting, 10 MQCONNs can occur accross the same socket so the TLS is only invoked once.

Callbacks for the same connection handle are serialised. And you may also want to look at the Thread Affinity option for MQCTLO which forces all callbacks to the same thread. So you may not need multiple connections for this scenario.

sasbury commented 5 years ago

I was thinking over the weekend, that I hate eating the warning as well, i wonder if the right fix is to set the qmgr name earlier, so that if you get an error you have that set, but you can also check the error and decide if you want to proceed?

It also sounds like I could set sharecnv to 1 and get a new connection each time in my specific case, or possibly thread affinity, although I am worried about throughput if I do everything from 1 thread.

sasbury commented 5 years ago

Hm, now that I am looking i see that the qmgr name is an unsafe pointer, so setting that early isn't great. I will dig a bit more.

I am in a situation where I don't control the server config, but I do control all my connections so I am ok with the warning, but can't force the SHARECNV change.

If you have thoughts on the qmgr name, let me know, otherwise I may just kill the PR.

sasbury commented 5 years ago

One issue with leaving this all up to the caller is that I can't get to the hconn to see if it is -1. Do you think this is reasonable:

qMgr, err := ibmmq.Connx(qMgrName, connectionOptions)

if err != nil {
    mqret := err.(*ibmmq.MQReturn)
    if mqret.MQCC == ibmmq.MQCC_WARNING && mqret.MQRC == ibmmq.MQRC_SSL_ALREADY_INITIALIZED {

        // double check the connection went through
        cmho := ibmmq.NewMQCMHO()
        _, err2 := qMgr.CrtMH(cmho)
        if err2 != nil {
            return nil, err
        }

        return &qMgr, nil
    }
    return nil, err
}
ibmmqmet commented 5 years ago

If the MQCC is WARNING, then the connection has been made. The hConn will not be -1.

sasbury commented 5 years ago

Are you ok with closing this issue and the pull request? If so I will close them, or you can.

Thanks for the help! I got the fix in on my side, and things are purring along.